Bottom and top bar refinement (#2303)

Co-authored-by: Nahiyan Khan <nahiyan@squareup.com>
This commit is contained in:
Zane
2025-05-05 08:48:43 -07:00
committed by GitHub
parent 8ba40bdccc
commit a812d6ff79
26 changed files with 221 additions and 219 deletions

View File

@@ -37,7 +37,7 @@
"express": "^4.21.1", "express": "^4.21.1",
"framer-motion": "^11.11.11", "framer-motion": "^11.11.11",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.454.0", "lucide-react": "^0.475.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",
@@ -11454,12 +11454,12 @@
} }
}, },
"node_modules/lucide-react": { "node_modules/lucide-react": {
"version": "0.454.0", "version": "0.475.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz",
"integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==", "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==",
"license": "ISC", "license": "ISC",
"peerDependencies": { "peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
} }
}, },
"node_modules/magic-string": { "node_modules/magic-string": {

View File

@@ -109,7 +109,7 @@
"express": "^4.21.1", "express": "^4.21.1",
"framer-motion": "^11.11.11", "framer-motion": "^11.11.11",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"lucide-react": "^0.454.0", "lucide-react": "^0.475.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^5.3.0", "react-icons": "^5.3.0",

View File

@@ -1,8 +1,10 @@
import React, { useRef, useState, useEffect, useCallback } from 'react'; import React, { useRef, useState, useEffect, useCallback } from 'react';
import { Button } from './ui/button'; import { Button } from './ui/button';
import type { View } from '../App';
import Stop from './ui/Stop'; import Stop from './ui/Stop';
import { Attach, Send } from './icons'; import { Attach, Send } from './icons';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import BottomMenu from './bottom_menu/BottomMenu';
interface InputProps { interface InputProps {
handleSubmit: (e: React.FormEvent) => void; handleSubmit: (e: React.FormEvent) => void;
@@ -11,6 +13,8 @@ interface InputProps {
commandHistory?: string[]; commandHistory?: string[];
initialValue?: string; initialValue?: string;
droppedFiles?: string[]; droppedFiles?: string[];
setView: (view: View) => void;
numTokens?: number;
} }
export default function Input({ export default function Input({
@@ -19,10 +23,13 @@ export default function Input({
onStop, onStop,
commandHistory = [], commandHistory = [],
initialValue = '', initialValue = '',
setView,
numTokens,
droppedFiles = [], droppedFiles = [],
}: InputProps) { }: InputProps) {
const [_value, setValue] = useState(initialValue); const [_value, setValue] = useState(initialValue);
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
const [isFocused, setIsFocused] = useState(false);
// Update internal value when initialValue changes // Update internal value when initialValue changes
useEffect(() => { useEffect(() => {
@@ -205,10 +212,14 @@ export default function Input({
}; };
return ( return (
<form <div
onSubmit={onFormSubmit} className={`flex flex-col relative h-auto border rounded-lg transition-colors ${
className="flex relative h-auto px-[16px] pr-[68px] py-[1rem] border-t border-borderSubtle" isFocused
? 'border-borderProminent hover:border-borderProminent'
: 'border-borderSubtle hover:border-borderStandard'
} bg-bgApp z-10`}
> >
<form onSubmit={onFormSubmit}>
<textarea <textarea
data-testid="chat-input" data-testid="chat-input"
autoFocus autoFocus
@@ -219,6 +230,8 @@ export default function Input({
onCompositionStart={handleCompositionStart} onCompositionStart={handleCompositionStart}
onCompositionEnd={handleCompositionEnd} onCompositionEnd={handleCompositionEnd}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
ref={textAreaRef} ref={textAreaRef}
rows={1} rows={1}
style={{ style={{
@@ -226,17 +239,9 @@ export default function Input({
maxHeight: `${maxHeight}px`, maxHeight: `${maxHeight}px`,
overflowY: 'auto', overflowY: 'auto',
}} }}
className="w-full outline-none border-none focus:ring-0 bg-transparent p-0 text-base resize-none text-textStandard" className="w-full pl-4 pr-[68px] outline-none border-none focus:ring-0 bg-transparent pt-3 pb-1.5 text-sm resize-none text-textStandard"
/> />
<Button
type="button"
size="icon"
variant="ghost"
onClick={handleFileSelect}
className="absolute right-[40px] top-1/2 -translate-y-1/2 text-textSubtle hover:text-textStandard"
>
<Attach />
</Button>
{isLoading ? ( {isLoading ? (
<Button <Button
type="button" type="button"
@@ -247,7 +252,7 @@ export default function Input({
e.stopPropagation(); e.stopPropagation();
onStop?.(); onStop?.();
}} }}
className="absolute right-2 top-1/2 -translate-y-1/2 [&_svg]:size-5 text-textSubtle hover:text-textStandard" className="absolute right-3 top-2 text-textSubtle rounded-full border border-borderSubtle hover:border-borderStandard hover:text-textStandard w-7 h-7 [&_svg]:size-4"
> >
<Stop size={24} /> <Stop size={24} />
</Button> </Button>
@@ -257,13 +262,32 @@ export default function Input({
size="icon" size="icon"
variant="ghost" variant="ghost"
disabled={!displayValue.trim()} disabled={!displayValue.trim()}
className={`absolute right-2 top-1/2 -translate-y-1/2 text-textSubtle hover:text-textStandard ${ className={`absolute right-3 top-2 transition-colors rounded-full hover:cursor w-7 h-7 [&_svg]:size-4 ${
!displayValue.trim() ? 'text-textSubtle cursor-not-allowed' : '' !displayValue.trim()
? 'text-textSubtle cursor-not-allowed'
: 'bg-bgAppInverse text-white'
}`} }`}
> >
<Send /> <Send />
</Button> </Button>
)} )}
</form> </form>
<div className="flex items-center transition-colors text-textSubtle relative text-xs p-2 pr-3 border-t border-borderSubtle gap-2">
<div className="gap-1 flex items-center justify-between w-full">
<Button
type="button"
size="icon"
variant="ghost"
onClick={handleFileSelect}
className="text-textSubtle hover:text-textStandard w-7 h-7 [&_svg]:size-4"
>
<Attach />
</Button>
<BottomMenu setView={setView} numTokens={numTokens} />
</div>
</div>
</div>
); );
} }

View File

@@ -1,9 +1,7 @@
import React, { useEffect, useRef, useState, useMemo } from 'react'; import React, { useEffect, useRef, useState, useMemo } from 'react';
import { getApiUrl } from '../config'; import { getApiUrl } from '../config';
import BottomMenu from './bottom_menu/BottomMenu';
import FlappyGoose from './FlappyGoose'; import FlappyGoose from './FlappyGoose';
import GooseMessage from './GooseMessage'; import GooseMessage from './GooseMessage';
import Input from './Input';
import { type View, ViewOptions } from '../App'; import { type View, ViewOptions } from '../App';
import LoadingGoose from './LoadingGoose'; import LoadingGoose from './LoadingGoose';
import MoreMenuLayout from './more_menu/MoreMenuLayout'; import MoreMenuLayout from './more_menu/MoreMenuLayout';
@@ -34,6 +32,7 @@ import {
ToolResponseMessageContent, ToolResponseMessageContent,
ToolConfirmationRequestMessageContent, ToolConfirmationRequestMessageContent,
} from '../types/message'; } from '../types/message';
import ChatInput from './ChatInput';
export interface ChatType { export interface ChatType {
id: string; id: string;
@@ -469,9 +468,11 @@ function ChatContent({
<div className="flex flex-col w-full h-screen items-center justify-center"> <div className="flex flex-col w-full h-screen items-center justify-center">
{/* Loader when generating recipe */} {/* Loader when generating recipe */}
{isGeneratingRecipe && <LayingEggLoader />} {isGeneratingRecipe && <LayingEggLoader />}
<div className="relative flex items-center h-[36px] w-full"> <MoreMenuLayout
<MoreMenuLayout setView={setView} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} /> hasMessages={hasMessages}
</div> setView={setView}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
/>
<Card <Card
className="flex flex-col flex-1 rounded-none h-[calc(100vh-95px)] w-full bg-bgApp mt-0 border-none relative" className="flex flex-col flex-1 rounded-none h-[calc(100vh-95px)] w-full bg-bgApp mt-0 border-none relative"
@@ -559,21 +560,23 @@ function ChatContent({
</div> </div>
</div> </div>
)} )}
<div className="block h-16" /> <div className="block h-8" />
</ScrollArea> </ScrollArea>
)} )}
<div className="relative"> <div className="relative p-4 pt-0 z-10 animate-[fadein_400ms_ease-in_forwards]">
{isLoading && <LoadingGoose />} {isLoading && <LoadingGoose />}
<Input <ChatInput
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
isLoading={isLoading} isLoading={isLoading}
onStop={onStopGoose} onStop={onStopGoose}
commandHistory={commandHistory} commandHistory={commandHistory}
initialValue={_input} initialValue={_input}
setView={setView}
hasMessages={hasMessages}
numTokens={sessionTokenCount}
droppedFiles={droppedFiles} droppedFiles={droppedFiles}
/> />
<BottomMenu hasMessages={hasMessages} setView={setView} numTokens={sessionTokenCount} />
</div> </div>
</Card> </Card>

View File

@@ -6,7 +6,7 @@ const LoadingGoose = () => {
<div className="w-full pb-[2px]"> <div className="w-full pb-[2px]">
<div <div
data-testid="loading-indicator" data-testid="loading-indicator"
className="flex items-center text-xs text-textStandard mb-2 mt-2 pl-4 animate-[appear_250ms_ease-in_forwards]" className="flex items-center text-xs text-textStandard mb-2 mt-2 animate-[appear_250ms_ease-in_forwards]"
> >
<GooseLogo className="mr-2" size="small" hover={false} /> <GooseLogo className="mr-2" size="small" hover={false} />
goose is working on it goose is working on it

View File

@@ -3,6 +3,7 @@ import { ProviderGrid } from './ProviderGrid';
import { ScrollArea } from './ui/scroll-area'; import { ScrollArea } from './ui/scroll-area';
import { Button } from './ui/button'; import { Button } from './ui/button';
import WelcomeGooseLogo from './WelcomeGooseLogo'; import WelcomeGooseLogo from './WelcomeGooseLogo';
import MoreMenuLayout from './more_menu/MoreMenuLayout';
// Extending React CSSProperties to include custom webkit property // Extending React CSSProperties to include custom webkit property
declare module 'react' { declare module 'react' {
@@ -18,11 +19,7 @@ interface WelcomeScreenProps {
export default function WelcomeScreen({ onSubmit }: WelcomeScreenProps) { export default function WelcomeScreen({ onSubmit }: WelcomeScreenProps) {
return ( return (
<div className="h-screen w-full select-none bg-white dark:bg-black"> <div className="h-screen w-full select-none bg-white dark:bg-black">
{/* Draggable title bar region */} <MoreMenuLayout showMenu={false} />
<div
className="relative flex items-center h-[36px] w-full bg-bgSubtle"
style={{ WebkitAppRegion: 'drag' }}
></div>
{/* Content area - explicitly set as non-draggable */} {/* Content area - explicitly set as non-draggable */}
<div <div

View File

@@ -5,25 +5,22 @@ import { AlertType, useAlerts } from '../alerts';
import { useToolCount } from '../alerts/useToolCount'; import { useToolCount } from '../alerts/useToolCount';
import BottomMenuAlertPopover from './BottomMenuAlertPopover'; import BottomMenuAlertPopover from './BottomMenuAlertPopover';
import { ModelRadioList } from '../settings/models/ModelRadioList'; import { ModelRadioList } from '../settings/models/ModelRadioList';
import { Document, ChevronUp, ChevronDown } from '../icons'; import { ChevronUp, ChevronDown } from '../icons';
import type { View, ViewOptions } from '../../App'; import type { View, ViewOptions } from '../../App';
import { settingsV2Enabled } from '../../flags'; import { settingsV2Enabled } from '../../flags';
import { BottomMenuModeSelection } from './BottomMenuModeSelection'; import { BottomMenuModeSelection } from './BottomMenuModeSelection';
import ModelsBottomBar from '../settings_v2/models/bottom_bar/ModelsBottomBar'; import ModelsBottomBar from '../settings_v2/models/bottom_bar/ModelsBottomBar';
import { useConfig } from '../ConfigContext'; import { useConfig } from '../ConfigContext';
import { getCurrentModelAndProvider } from '../settings_v2/models'; import { getCurrentModelAndProvider } from '../settings_v2/models';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip';
const TOKEN_LIMIT_DEFAULT = 128000; // fallback for custom models that the backend doesn't know about const TOKEN_LIMIT_DEFAULT = 128000; // fallback for custom models that the backend doesn't know about
const TOKEN_WARNING_THRESHOLD = 0.8; // warning shows at 80% of the token limit const TOKEN_WARNING_THRESHOLD = 0.8; // warning shows at 80% of the token limit
const TOOLS_MAX_SUGGESTED = 60; // max number of tools before we show a warning const TOOLS_MAX_SUGGESTED = 60; // max number of tools before we show a warning
export default function BottomMenu({ export default function BottomMenu({
hasMessages,
setView, setView,
numTokens = 0, numTokens = 0,
}: { }: {
hasMessages: boolean;
setView: (view: View, viewOptions?: ViewOptions) => void; setView: (view: View, viewOptions?: ViewOptions) => void;
numTokens?: number; numTokens?: number;
}) { }) {
@@ -34,10 +31,6 @@ export default function BottomMenu({
const toolCount = useToolCount(); const toolCount = useToolCount();
const { getProviders, read } = useConfig(); const { getProviders, read } = useConfig();
const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT); const [tokenLimit, setTokenLimit] = useState<number>(TOKEN_LIMIT_DEFAULT);
const [isDirTruncated, setIsDirTruncated] = useState(false);
// eslint-disable-next-line no-undef
const dirRef = useRef<HTMLSpanElement>(null);
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
// Load providers and get current model's token limit // Load providers and get current model's token limit
const loadProviderDetails = async () => { const loadProviderDetails = async () => {
@@ -142,62 +135,12 @@ export default function BottomMenu({
}; };
}, [isModelMenuOpen]); }, [isModelMenuOpen]);
useEffect(() => {
const checkTruncation = () => {
if (dirRef.current) {
setIsDirTruncated(dirRef.current.scrollWidth > dirRef.current.clientWidth);
}
};
checkTruncation();
window.addEventListener('resize', checkTruncation);
return () => window.removeEventListener('resize', checkTruncation);
}, []);
useEffect(() => {
setIsTooltipOpen(false);
}, [isDirTruncated]);
return ( return (
<div className="flex justify-between items-center text-textSubtle relative bg-bgSubtle border-t border-borderSubtle text-xs pl-4 h-[40px] pb-1 align-middle"> <div className="flex justify-between items-center transition-colors text-textSubtle relative text-xs align-middle">
{/* Directory Chooser - Always visible */} <div className="flex items-center pl-2">
<span
className="cursor-pointer flex items-center [&>svg]:size-4"
onClick={async () => {
if (hasMessages) {
window.electron.directoryChooser();
} else {
window.electron.directoryChooser(true);
}
}}
>
<Document className="mr-1" />
<TooltipProvider>
<Tooltip open={isTooltipOpen} onOpenChange={setIsTooltipOpen}>
<TooltipTrigger asChild>
<span
ref={dirRef}
className="max-w-[170px] md:max-w-[200px] lg:max-w-[380px] min-w-0 block overflow-hidden text-ellipsis whitespace-nowrap [direction:rtl] text-left"
>
{window.appConfig.get('GOOSE_WORKING_DIR') as string}
</span>
</TooltipTrigger>
{isDirTruncated && (
<TooltipContent className="max-w-96 overflow-auto scrollbar-thin" side="top">
{window.appConfig.get('GOOSE_WORKING_DIR') as string}
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
<ChevronUp className="ml-1" />
</span>
{/* Goose Mode Selector Dropdown */}
<BottomMenuModeSelection setView={setView} />
{/* Right-side section with ToolCount and Model Selector together */}
<div className="flex items-center mr-4 space-x-1">
{/* Tool and Token count */} {/* Tool and Token count */}
{<BottomMenuAlertPopover alerts={alerts} />} {<BottomMenuAlertPopover alerts={alerts} />}
{/* Model Selector Dropdown */} {/* Model Selector Dropdown */}
{settingsV2Enabled ? ( {settingsV2Enabled ? (
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} /> <ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
@@ -262,13 +205,19 @@ export default function BottomMenu({
}} }}
> >
<span className="text-sm">Tools and Settings</span> <span className="text-sm">Tools and Settings</span>
<Sliders className="w-5 h-5 ml-2 rotate-90" /> <Sliders className="w-4 h-4 ml-2 rotate-90" />
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
)} )}
{/* Separator */}
<div className="w-[1px] h-4 bg-borderSubtle mx-2" />
{/* Goose Mode Selector Dropdown */}
<BottomMenuModeSelection setView={setView} />
</div> </div>
</div> </div>
); );

View File

@@ -1,10 +1,10 @@
import React, { useEffect, useRef, useState, useCallback } from 'react'; import React, { useEffect, useRef, useState, useCallback } from 'react';
import { getApiUrl, getSecretKey } from '../../config'; import { getApiUrl, getSecretKey } from '../../config';
import { ChevronDown, ChevronUp } from '../icons';
import { all_goose_modes, ModeSelectionItem } from '../settings_v2/mode/ModeSelectionItem'; import { all_goose_modes, ModeSelectionItem } from '../settings_v2/mode/ModeSelectionItem';
import { useConfig } from '../ConfigContext'; import { useConfig } from '../ConfigContext';
import { settingsV2Enabled } from '../../flags'; import { settingsV2Enabled } from '../../flags';
import { View, ViewOptions } from '../App'; import { View, ViewOptions } from '../../App';
import { Orbit } from 'lucide-react';
interface BottomMenuModeSelectionProps { interface BottomMenuModeSelectionProps {
setView: (view: View, viewOptions?: ViewOptions) => void; setView: (view: View, viewOptions?: ViewOptions) => void;
@@ -119,23 +119,25 @@ export const BottomMenuModeSelection = ({ setView }: BottomMenuModeSelectionProp
return ( return (
<div className="relative flex items-center" ref={gooseModeDropdownRef}> <div className="relative flex items-center" ref={gooseModeDropdownRef}>
<div <button
className="flex items-center cursor-pointer" className="flex items-center justify-center text-textSubtle hover:text-textStandard h-6 [&_svg]:size-4"
onClick={() => setIsGooseModeMenuOpen(!isGooseModeMenuOpen)} onClick={() => setIsGooseModeMenuOpen(!isGooseModeMenuOpen)}
> >
<span className="truncate max-w-[170px] md:max-w-[200px] lg:max-w-[380px]"> <span className="pr-1.5">{getValueByKey(gooseMode).toLowerCase()}</span>
Goose Mode: {getValueByKey(gooseMode)} <Orbit />
</span> {/*<span className="truncate max-w-[170px] md:max-w-[200px] lg:max-w-[380px]">*/}
{isGooseModeMenuOpen ? ( {/* Goose Mode: {getValueByKey(gooseMode)}*/}
<ChevronDown className="w-4 h-4 ml-1" /> {/*</span>*/}
) : ( {/*{isGooseModeMenuOpen ? (*/}
<ChevronUp className="w-4 h-4 ml-1" /> {/* <ChevronDown className="w-4 h-4 ml-1" />*/}
)} {/*) : (*/}
</div> {/* <ChevronUp className="w-4 h-4 ml-1" />*/}
{/*)}*/}
</button>
{/* Dropdown Menu */} {/* Dropdown Menu */}
{isGooseModeMenuOpen && ( {isGooseModeMenuOpen && (
<div className="absolute bottom-[24px] pl-4 pt-2 right-0 w-[240px] bg-bgApp rounded-lg border border-borderSubtle"> <div className="absolute bottom-[24px] right-0 w-[240px] py-2 bg-bgApp rounded-lg border border-borderSubtle">
<div> <div>
{all_goose_modes.map((mode) => ( {all_goose_modes.map((mode) => (
<ModeSelectionItem <ModeSelectionItem

View File

@@ -1,6 +1,6 @@
import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '../ui/popover'; import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '../ui/popover';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ChatSmart, Idea, More, Refresh, Time, Send } from '../icons'; import { ChatSmart, Idea, Refresh, Time, Send, Settings } from '../icons';
import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react'; import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react';
import { useConfig } from '../ConfigContext'; import { useConfig } from '../ConfigContext';
import { settingsV2Enabled } from '../../flags'; import { settingsV2Enabled } from '../../flags';
@@ -169,10 +169,10 @@ export default function MoreMenu({
<PopoverTrigger asChild> <PopoverTrigger asChild>
<button <button
data-testid="more-options-button" data-testid="more-options-button"
className={`z-[100] absolute top-2 right-4 w-[20px] h-[20px] transition-colors cursor-pointer no-drag hover:text-textProminent ${open ? 'text-textProminent' : 'text-textSubtle'}`} className={`z-[100] w-7 h-7 p-1 rounded-full border border-borderSubtle transition-colors cursor-pointer no-drag hover:text-textStandard hover:border-borderStandard ${open ? 'text-textStandard' : 'text-textSubtle'}`}
role="button" role="button"
> >
<More /> <Settings />
</button> </button>
</PopoverTrigger> </PopoverTrigger>

View File

@@ -1,22 +1,56 @@
import { useState } from 'react';
import MoreMenu from './MoreMenu'; import MoreMenu from './MoreMenu';
import React from 'react'; import type { View, ViewOptions } from '../../App';
import { View, ViewOptions } from '../../App'; import { Document } from '../icons';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip';
export default function MoreMenuLayout({ export default function MoreMenuLayout({
hasMessages,
showMenu = true,
setView, setView,
setIsGoosehintsModalOpen, setIsGoosehintsModalOpen,
}: { }: {
setView: (view: View, viewOptions?: ViewOptions) => void; hasMessages?: boolean;
setIsGoosehintsModalOpen: (isOpen: boolean) => void; showMenu?: boolean;
setView?: (view: View, viewOptions?: ViewOptions) => void;
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
}) { }) {
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
return ( return (
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle border-b border-borderSubtle"> <div
<div className="flex-1"></div> className="relative flex items-center h-14 border-b border-borderSubtle w-full"
<div className="flex items-center h-full"> style={{ WebkitAppRegion: 'drag' }}
<div className="flex items-center justify-center h-full px-2 mr-2"> >
{showMenu && (
<div className="flex items-center justify-between w-full h-full pl-[86px] pr-4">
<TooltipProvider>
<Tooltip open={isTooltipOpen} onOpenChange={setIsTooltipOpen}>
<TooltipTrigger asChild>
<button
className="z-[100] no-drag hover:cursor-pointer border border-subtle hover:border-borderStandard rounded-lg p-2 pr-3 text-textSubtle hover:text-textStandard text-sm flex items-center transition-colors [&>svg]:size-4 "
onClick={async () => {
if (hasMessages) {
window.electron.directoryChooser();
} else {
window.electron.directoryChooser(true);
}
}}
>
<Document className="mr-1" />
<div className="max-w-[200px] truncate [direction:rtl]">
{window.appConfig.get('GOOSE_WORKING_DIR')}
</div>
</button>
</TooltipTrigger>
<TooltipContent className="max-w-96 overflow-auto scrollbar-thin" side="top">
{window.appConfig.get('GOOSE_WORKING_DIR') as string}
</TooltipContent>
</Tooltip>
</TooltipProvider>
<MoreMenu setView={setView} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} /> <MoreMenu setView={setView} setIsGoosehintsModalOpen={setIsGoosehintsModalOpen} />
</div> </div>
</div> )}
</div> </div>
); );
} }

View File

@@ -17,6 +17,7 @@ import { createSharedSession } from '../../sharedSessions';
import { Modal, ModalContent } from '../ui/modal'; import { Modal, ModalContent } from '../ui/modal';
import { Button } from '../ui/button'; import { Button } from '../ui/button';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
interface SessionHistoryViewProps { interface SessionHistoryViewProps {
session: SessionDetails; session: SessionDetails;
@@ -109,7 +110,7 @@ const SessionHistoryView: React.FC<SessionHistoryViewProps> = ({
return ( return (
<div className="h-screen w-full flex flex-col"> <div className="h-screen w-full flex flex-col">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
{/* Top Row - back, info, reopen thread (fixed) */} {/* Top Row - back, info, reopen thread (fixed) */}
<SessionHeaderCard onBack={onBack}> <SessionHeaderCard onBack={onBack}>

View File

@@ -15,6 +15,7 @@ import BackButton from '../ui/BackButton';
import { ScrollArea } from '../ui/scroll-area'; import { ScrollArea } from '../ui/scroll-area';
import { View, ViewOptions } from '../../App'; import { View, ViewOptions } from '../../App';
import { formatMessageTimestamp } from '../../utils/timeUtils'; import { formatMessageTimestamp } from '../../utils/timeUtils';
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
interface SessionListViewProps { interface SessionListViewProps {
setView: (view: View, viewOptions?: ViewOptions) => void; setView: (view: View, viewOptions?: ViewOptions) => void;
@@ -48,7 +49,7 @@ const SessionListView: React.FC<SessionListViewProps> = ({ setView, onSelectSess
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="flex flex-col pb-24"> <div className="flex flex-col pb-24">

View File

@@ -18,6 +18,7 @@ import { View, ViewOptions } from '../../App';
import { ModeSelection } from './basic/ModeSelection'; import { ModeSelection } from './basic/ModeSelection';
import SessionSharingSection from './session/SessionSharingSection'; import SessionSharingSection from './session/SessionSharingSection';
import { toastSuccess } from '../../toasts'; import { toastSuccess } from '../../toasts';
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
const EXTENSIONS_DESCRIPTION = const EXTENSIONS_DESCRIPTION =
'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.'; 'The Model Context Protocol (MCP) is a system that allows AI models to securely connect with local or remote resources using standard server setups. It works like a client-server setup and expands AI capabilities using three main components: Prompts, Resources, and Tools.';
@@ -190,7 +191,7 @@ export default function SettingsView({
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="flex flex-col pb-24"> <div className="flex flex-col pb-24">

View File

@@ -11,7 +11,7 @@ export interface GooseMode {
export const all_goose_modes: GooseMode[] = [ export const all_goose_modes: GooseMode[] = [
{ {
key: 'auto', key: 'auto',
label: 'Completely Autonomous', label: 'Autonomous',
description: 'Full file modification capabilities, edit, create, and delete files freely.', description: 'Full file modification capabilities, edit, create, and delete files freely.',
}, },
{ {
@@ -90,18 +90,15 @@ export function ModeSelectionItem({
return ( return (
<div> <div>
<div <div className="group hover:cursor-pointer" onClick={() => handleModeChange(mode.key)}>
className="flex items-center justify-between p-2 text-textStandard hover:bg-bgSubtle transition-colors" <div className="flex items-center justify-between text-textStandard mb-4">
onClick={() => handleModeChange(mode.key)} <div className="flex">
> <h3 className="text-textStandard">{mode.label}</h3>
<div>
<h3 className="text-sm font-light text-textStandard dark:text-gray-200">{mode.label}</h3>
{showDescription && ( {showDescription && (
<p className="text-xs text-textSubtle dark:text-gray-400 mt-[2px]"> <p className="text-xs text-textSubtle mt-[2px]">{mode.description}</p>
{mode.description}
</p>
)} )}
</div> </div>
</div>
<div className="relative flex items-center gap-3"> <div className="relative flex items-center gap-3">
{!isApproveModeConfigure && (mode.key == 'approve' || mode.key == 'smart_approve') && ( {!isApproveModeConfigure && (mode.key == 'approve' || mode.key == 'smart_approve') && (
<button <button

View File

@@ -6,6 +6,7 @@ import { SearchBar } from './Search';
import { AddModelInline } from './AddModelInline'; import { AddModelInline } from './AddModelInline';
import { ScrollArea } from '../../ui/scroll-area'; import { ScrollArea } from '../../ui/scroll-area';
import type { View } from '../../../App'; import type { View } from '../../../App';
import MoreMenuLayout from '../../more_menu/MoreMenuLayout';
export default function MoreModelsView({ export default function MoreModelsView({
onClose, onClose,
@@ -16,7 +17,7 @@ export default function MoreModelsView({
}) { }) {
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="px-8 pt-6 pb-4"> <div className="px-8 pt-6 pb-4">

View File

@@ -2,11 +2,12 @@ import React from 'react';
import { ScrollArea } from '../../ui/scroll-area'; import { ScrollArea } from '../../ui/scroll-area';
import BackButton from '../../ui/BackButton'; import BackButton from '../../ui/BackButton';
import { ConfigureProvidersGrid } from './ConfigureProvidersGrid'; import { ConfigureProvidersGrid } from './ConfigureProvidersGrid';
import MoreMenuLayout from '../../more_menu/MoreMenuLayout';
export default function ConfigureProvidersView({ onClose }: { onClose: () => void }) { export default function ConfigureProvidersView({ onClose }: { onClose: () => void }) {
return ( return (
<div className="h-screen w-full"> <div className="h-screen w-full">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="px-8 pt-6 pb-4"> <div className="px-8 pt-6 pb-4">

View File

@@ -6,6 +6,7 @@ import ModelsSection from './models/ModelsSection';
import { ModeSection } from './mode/ModeSection'; import { ModeSection } from './mode/ModeSection';
import SessionSharingSection from './sessions/SessionSharingSection'; import SessionSharingSection from './sessions/SessionSharingSection';
import { ExtensionConfig } from '../../api'; import { ExtensionConfig } from '../../api';
import MoreMenuLayout from '../more_menu/MoreMenuLayout';
export type SettingsViewOptions = { export type SettingsViewOptions = {
deepLinkConfig?: ExtensionConfig; deepLinkConfig?: ExtensionConfig;
@@ -23,7 +24,7 @@ export default function SettingsView({
}) { }) {
return ( return (
<div className="h-screen w-full animate-[fadein_200ms_ease-in_forwards]"> <div className="h-screen w-full animate-[fadein_200ms_ease-in_forwards]">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="flex flex-col pb-24"> <div className="flex flex-col pb-24">

View File

@@ -1,6 +1,5 @@
import { Input } from '../../../ui/input'; import { Input } from '../../../ui/input';
import { Select } from '../../../ui/Select'; import { Select } from '../../../ui/Select';
import React from 'react';
interface ExtensionInfoFieldsProps { interface ExtensionInfoFieldsProps {
name: string; name: string;

View File

@@ -12,17 +12,17 @@ export interface GooseMode {
export const all_goose_modes: GooseMode[] = [ export const all_goose_modes: GooseMode[] = [
{ {
key: 'auto', key: 'auto',
label: 'Completely autonomous', label: 'Autonomous',
description: 'Full file modification capabilities, edit, create, and delete files freely.', description: 'Full file modification capabilities, edit, create, and delete files freely.',
}, },
{ {
key: 'approve', key: 'approve',
label: 'Manual approval', label: 'Manual',
description: 'All tools, extensions and file modifications will require human approval', description: 'All tools, extensions and file modifications will require human approval',
}, },
{ {
key: 'smart_approve', key: 'smart_approve',
label: 'Smart approval', label: 'Smart',
description: 'Intelligently determine which actions need approval based on risk level ', description: 'Intelligently determine which actions need approval based on risk level ',
}, },
{ {
@@ -61,21 +61,19 @@ export function ModeSelectionItem({
return ( return (
<div className="group hover:cursor-pointer"> <div className="group hover:cursor-pointer">
<div <div
className="flex items-center justify-between text-textStandard mb-4" className="flex items-center justify-between text-textStandard py-2 px-4 hover:bg-bgSubtle"
onClick={() => handleModeChange(mode.key)} onClick={() => handleModeChange(mode.key)}
> >
<div className="flex"> <div className="flex">
<div> <div>
<h3 className="text-textStandard dark:text-gray-200">{mode.label}</h3> <h3 className="text-textStandard">{mode.label}</h3>
{showDescription && ( {showDescription && (
<p className="text-xs text-textSubtle dark:text-gray-400 mt-[2px]"> <p className="text-xs text-textSubtle mt-[2px]">{mode.description}</p>
{mode.description}
</p>
)} )}
</div> </div>
</div> </div>
<div className="relative flex items-center gap-3 mr-4"> <div className="relative flex items-center gap-2">
{!isApproveModeConfigure && (mode.key == 'approve' || mode.key == 'smart_approve') && ( {!isApproveModeConfigure && (mode.key == 'approve' || mode.key == 'smart_approve') && (
<button <button
onClick={() => { onClick={() => {
@@ -84,7 +82,7 @@ export function ModeSelectionItem({
}); });
}} }}
> >
<Gear className="w-5 h-5 text-textSubtle hover:text-textStandard" /> <Gear className="w-4 h-4 text-textSubtle hover:text-textStandard" />
</button> </button>
)} )}
<input <input

View File

@@ -1,4 +1,3 @@
import { ChevronDown, ChevronUp } from '../../../icons';
import { Sliders } from 'lucide-react'; import { Sliders } from 'lucide-react';
import React, { useEffect, useState, useRef } from 'react'; import React, { useEffect, useState, useRef } from 'react';
import { useConfig } from '../../../ConfigContext'; import { useConfig } from '../../../ConfigContext';
@@ -69,10 +68,10 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
}, [isModelMenuOpen]); }, [isModelMenuOpen]);
return ( return (
<div className="relative flex items-center ml-auto mr-4" ref={dropdownRef}> <div className="relative flex items-center" ref={dropdownRef}>
<div ref={menuRef} className="relative"> <div ref={menuRef} className="relative">
<div <div
className="flex items-center cursor-pointer max-w-[180px] md:max-w-[200px] lg:max-w-[380px] min-w-0 group" className="flex items-center hover:cursor-pointer max-w-[180px] md:max-w-[200px] lg:max-w-[380px] min-w-0 group hover:text-textStandard transition-colors"
onClick={() => setIsModelMenuOpen(!isModelMenuOpen)} onClick={() => setIsModelMenuOpen(!isModelMenuOpen)}
> >
<TooltipProvider> <TooltipProvider>
@@ -92,23 +91,18 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
)} )}
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
{isModelMenuOpen ? (
<ChevronDown className="w-4 h-4 ml-1 flex-shrink-0" />
) : (
<ChevronUp className="w-4 h-4 ml-1 flex-shrink-0" />
)}
</div> </div>
{/* Dropdown Menu */} {/* Dropdown Menu */}
{isModelMenuOpen && ( {isModelMenuOpen && (
<div className="absolute bottom-[24px] right-0 w-[300px] bg-bgApp rounded-lg border border-borderSubtle"> <div className="absolute bottom-[24px] right-[-55px] w-[300px] bg-bgApp rounded-lg border border-borderSubtle">
<div className=""> <div className="">
<div className="text-sm text-textProminent mt-3 ml-2">Current:</div> <div className="text-sm text-textProminent mt-2 ml-2">Current:</div>
<div className="flex items-center justify-between text-sm ml-2"> <div className="flex items-center justify-between text-sm ml-2">
{model} -- {provider} {model} -- {provider}
</div> </div>
<div <div
className="flex items-center justify-between text-textStandard p-2 cursor-pointer hover:bg-bgStandard className="flex items-center justify-between text-textStandard p-2 cursor-pointer transition-colors hover:bg-bgStandard
border-t border-borderSubtle mt-2" border-t border-borderSubtle mt-2"
onClick={() => { onClick={() => {
setIsModelMenuOpen(false); setIsModelMenuOpen(false);
@@ -116,7 +110,7 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
}} }}
> >
<span className="text-sm">Change Model</span> <span className="text-sm">Change Model</span>
<Sliders className="w-5 h-5 ml-2 rotate-90" /> <Sliders className="w-4 h-4 ml-2 rotate-90" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState, useCallback } from 'react'; import { useEffect, useState, useCallback } from 'react';
import { ArrowLeftRight, ExternalLink } from 'lucide-react'; import { ArrowLeftRight, ExternalLink } from 'lucide-react';
import Modal from '../../../Modal'; import Modal from '../../../Modal';

View File

@@ -4,6 +4,7 @@ import BackButton from '../../ui/BackButton';
import { FixedExtensionEntry, useConfig } from '../../ConfigContext'; import { FixedExtensionEntry, useConfig } from '../../ConfigContext';
import { ChevronRight } from 'lucide-react'; import { ChevronRight } from 'lucide-react';
import PermissionModal from './PermissionModal'; import PermissionModal from './PermissionModal';
import MoreMenuLayout from '../../more_menu/MoreMenuLayout';
function RuleItem({ title, description }: { title: string; description: string }) { function RuleItem({ title, description }: { title: string; description: string }) {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
@@ -76,7 +77,7 @@ export default function PermissionSettingsView({ onClose }: { onClose: () => voi
return ( return (
<div className="h-screen w-full animate-[fadein_200ms_ease-in_forwards]"> <div className="h-screen w-full animate-[fadein_200ms_ease-in_forwards]">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="h-full w-full"> <ScrollArea className="h-full w-full">
<div className="flex flex-col pb-24"> <div className="flex flex-col pb-24">

View File

@@ -173,7 +173,8 @@ export const PROVIDER_REGISTRY: ProviderRegistry[] = [
details: { details: {
id: 'azure_openai', id: 'azure_openai',
name: 'Azure OpenAI', name: 'Azure OpenAI',
description: 'Access Azure OpenAI models using API key or Azure credentials. If no API key is provided, Azure credential chain will be used.', description:
'Access Azure OpenAI models using API key or Azure credentials. If no API key is provided, Azure credential chain will be used.',
parameters: [ parameters: [
{ {
name: 'AZURE_OPENAI_API_KEY', name: 'AZURE_OPENAI_API_KEY',

View File

@@ -7,6 +7,7 @@ import { ProviderDetails } from '../../../api/types.gen';
import { initializeSystem } from '../../../utils/providerUtils'; import { initializeSystem } from '../../../utils/providerUtils';
import WelcomeGooseLogo from '../../WelcomeGooseLogo'; import WelcomeGooseLogo from '../../WelcomeGooseLogo';
import { toastService } from '../../../toasts'; import { toastService } from '../../../toasts';
import MoreMenuLayout from '../../more_menu/MoreMenuLayout';
interface ProviderSettingsProps { interface ProviderSettingsProps {
onClose: () => void; onClose: () => void;
@@ -88,7 +89,8 @@ export default function ProviderSettings({ onClose, isOnboarding }: ProviderSett
return ( return (
<div className="h-screen w-full flex flex-col"> <div className="h-screen w-full flex flex-col">
<div className="relative flex items-center h-[36px] w-full bg-bgSubtle"></div> <MoreMenuLayout showMenu={false} />
<ScrollArea className="flex-1 w-full"> <ScrollArea className="flex-1 w-full">
{isOnboarding && ( {isOnboarding && (
<div className="group/logo flex justify-left pl-8"> <div className="group/logo flex justify-left pl-8">

View File

@@ -97,12 +97,7 @@ const ScrollArea = React.forwardRef<ScrollAreaHandle, ScrollAreaProps>(
data-scrolled={isScrolled} data-scrolled={isScrolled}
{...props} {...props}
> >
<div <div className={cn('absolute top-0 left-0 right-0 z-10 transition-all duration-200')} />
className={cn(
'absolute top-0 left-0 right-0 z-10 transition-all duration-200',
isScrolled ? 'border-t border-borderSubtle' : 'border-t border-transparent'
)}
/>
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
ref={viewportRef} ref={viewportRef}
className="h-full w-full rounded-[inherit] [&>div]:!block" className="h-full w-full rounded-[inherit] [&>div]:!block"

View File

@@ -334,7 +334,7 @@ const createChat = async (
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default', titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default',
trafficLightPosition: process.platform === 'darwin' ? { x: 16, y: 10 } : undefined, trafficLightPosition: process.platform === 'darwin' ? { x: 16, y: 20 } : undefined,
vibrancy: process.platform === 'darwin' ? 'window' : undefined, vibrancy: process.platform === 'darwin' ? 'window' : undefined,
frame: process.platform === 'darwin' ? false : true, frame: process.platform === 'darwin' ? false : true,
width: 750, width: 750,