diff --git a/ui/desktop/components/ui/popover.tsx b/ui/desktop/components/ui/popover.tsx new file mode 100644 index 0000000000..d82e714959 --- /dev/null +++ b/ui/desktop/components/ui/popover.tsx @@ -0,0 +1,31 @@ +import * as React from "react" +import * as PopoverPrimitive from "@radix-ui/react-popover" + +import { cn } from "@/lib/utils" + +const Popover = PopoverPrimitive.Root + +const PopoverTrigger = PopoverPrimitive.Trigger + +const PopoverAnchor = PopoverPrimitive.Anchor + +const PopoverContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, align = "center", sideOffset = 4, ...props }, ref) => ( + + + +)) +PopoverContent.displayName = PopoverPrimitive.Content.displayName + +export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor } diff --git a/ui/desktop/package-lock.json b/ui/desktop/package-lock.json index 27b34ea6a4..f1a70549fd 100644 --- a/ui/desktop/package-lock.json +++ b/ui/desktop/package-lock.json @@ -16,6 +16,7 @@ "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.5", diff --git a/ui/desktop/package.json b/ui/desktop/package.json index 7a239690ae..fc6fb511ba 100644 --- a/ui/desktop/package.json +++ b/ui/desktop/package.json @@ -73,6 +73,7 @@ "@radix-ui/react-avatar": "^1.1.1", "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-popover": "^1.1.6", "@radix-ui/react-radio-group": "^1.2.3", "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-select": "^2.1.5", diff --git a/ui/desktop/src/components/ChatView.tsx b/ui/desktop/src/components/ChatView.tsx index df2720502b..f2c8d4abf3 100644 --- a/ui/desktop/src/components/ChatView.tsx +++ b/ui/desktop/src/components/ChatView.tsx @@ -265,7 +265,7 @@ export default function ChatView({ return (
-
+
diff --git a/ui/desktop/src/components/MoreMenu.tsx b/ui/desktop/src/components/MoreMenu.tsx index b9fd4996fe..8a8b209e21 100644 --- a/ui/desktop/src/components/MoreMenu.tsx +++ b/ui/desktop/src/components/MoreMenu.tsx @@ -1,6 +1,7 @@ -import { Popover, PopoverContent, PopoverTrigger, PopoverPortal } from '@radix-ui/react-popover'; +import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '../components/ui/popover'; import React, { useEffect, useState } from 'react'; -import { More } from './icons'; +import { ChatSmart, Idea, More, Refresh, Time } from './icons'; +import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react'; import { View } from '../App'; interface VersionInfo { @@ -8,7 +9,69 @@ interface VersionInfo { available_versions: string[]; } -// Accept setView as a prop from the parent (e.g. Chat) +interface MenuButtonProps { + onClick: () => void; + children: React.ReactNode; + subtitle?: string; + className?: string; + danger?: boolean; + icon?: React.ReactNode; +} + +const MenuButton: React.FC = ({ + onClick, + children, + subtitle, + className = '', + danger = false, + icon, +}) => ( + +); + +interface DarkModeToggleProps { + isDarkMode: boolean; + onToggle: () => void; +} + +const DarkModeToggle: React.FC = ({ isDarkMode, onToggle }) => ( + +); + export default function MoreMenu({ setView, setIsGoosehintsModalOpen, @@ -19,11 +82,9 @@ export default function MoreMenu({ const [open, setOpen] = useState(false); const [versions, setVersions] = useState(null); const [showVersions, setShowVersions] = useState(false); - const [useSystemTheme, setUseSystemTheme] = useState( () => localStorage.getItem('use_system_theme') === 'true' ); - const [isDarkMode, setDarkMode] = useState(() => { const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; if (useSystemTheme) { @@ -96,209 +157,119 @@ export default function MoreMenu({ } }; - const toggleUseSystemTheme = (event: React.ChangeEvent) => { - const checked = event.target.checked; - setUseSystemTheme(checked); - localStorage.setItem('use_system_theme', checked.toString()); - - if (checked) { - // If enabling system theme, immediately sync with system preference - const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - setDarkMode(systemPrefersDark); - localStorage.removeItem('theme'); // Remove manual theme setting - } - // If disabling system theme, keep current theme state but don't update localStorage yet - }; - const handleVersionSelect = (version: string) => { setOpen(false); setShowVersions(false); - // Create a new chat window with the selected version window.electron.createChatWindow(undefined, undefined, version); }; return ( - + - -
- {/*
- Use System Theme - -
*/} - {/* {!useSystemTheme && ( */} - - {/* )} */} - - {/* Versions Menu */} - {/* NOTE from alexhancock on 1/14/2025 - disabling temporarily until we figure out where this will go in settings */} - {false && versions && versions.available_versions.length > 0 && ( - <> - - {showVersions && ( -
- {versions.available_versions.map((version) => ( - - ))} -
- )} - - )} - -
setIsGoosehintsModalOpen(true)} - className="w-full text-left p-2 text-sm hover:bg-bgSubtle transition-colors cursor-pointer" - > - Configure .goosehints -
- - - - - - {/* View Previous Sessions */} - - - {/* Settings Menu */} - - - - {process.env.ALPHA && ( - - )} -
-
+ New session + ⌘N + + + { + setOpen(false); + window.electron.directoryChooser(); + }} + subtitle="Start a new session in a different directory" + icon={} + > + Open directory + ⌘O + + + setView('sessions')} + subtitle="View previous sessions and their contents" + icon={ + + setIsGoosehintsModalOpen(true)} + subtitle="Customize instructions" + icon={} + > + Configure .goosehints + + + + + { + setOpen(false); + setView('settings'); + }} + subtitle="View all settings and options" + icon={} + > + Advanced settings + ⌘, + + + { + localStorage.removeItem('GOOSE_PROVIDER'); + setOpen(false); + window.electron.createChatWindow(); + }} + danger + subtitle="Clear selected model and restart" + icon={} + className="border-b-0" + > + Reset provider and model + + + {process.env.ALPHA && ( + { + setOpen(false); + setView('alphaConfigureProviders'); + }} + className="text-indigo-800" + subtitle="Preview the new provider configuration interface" + > + See new providers grid + + )} +
+ + ); diff --git a/ui/desktop/src/components/icons/ChatSmart.tsx b/ui/desktop/src/components/icons/ChatSmart.tsx new file mode 100644 index 0000000000..9e901305c1 --- /dev/null +++ b/ui/desktop/src/components/icons/ChatSmart.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function ChatSmart({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/Idea.tsx b/ui/desktop/src/components/icons/Idea.tsx new file mode 100644 index 0000000000..20cfaa9ce4 --- /dev/null +++ b/ui/desktop/src/components/icons/Idea.tsx @@ -0,0 +1,22 @@ +import React from 'react'; + +export default function Idea({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/Refresh.tsx b/ui/desktop/src/components/icons/Refresh.tsx new file mode 100644 index 0000000000..312aa5e4aa --- /dev/null +++ b/ui/desktop/src/components/icons/Refresh.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function Refresh({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/Settings.tsx b/ui/desktop/src/components/icons/Settings.tsx new file mode 100644 index 0000000000..1c7fa18a1e --- /dev/null +++ b/ui/desktop/src/components/icons/Settings.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function Settings({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/Time.tsx b/ui/desktop/src/components/icons/Time.tsx new file mode 100644 index 0000000000..62459defce --- /dev/null +++ b/ui/desktop/src/components/icons/Time.tsx @@ -0,0 +1,20 @@ +import React from 'react'; + +export default function Time({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/index.tsx b/ui/desktop/src/components/icons/index.tsx index 3d3576c3b3..d62d48de6d 100644 --- a/ui/desktop/src/components/icons/index.tsx +++ b/ui/desktop/src/components/icons/index.tsx @@ -1,5 +1,6 @@ import Attach from './Attach'; import Back from './Back'; +import ChatSmart from './ChatSmart'; import Check from './Check'; import ChevronDown from './ChevronDown'; import ChevronUp from './ChevronUp'; @@ -7,15 +8,20 @@ import Close from './Close'; import Copy from './Copy'; import Document from './Document'; import Edit from './Edit'; +import Idea from './Idea'; import More from './More'; +import Refresh from './Refresh'; import SensitiveHidden from './SensitiveHidden'; import SensitiveVisible from './SensitiveVisible'; import Send from './Send'; +import Settings from './Settings'; +import Time from './Time'; import { Gear } from './Gear'; export { Attach, Back, + ChatSmart, Check, ChevronDown, ChevronUp, @@ -23,9 +29,13 @@ export { Copy, Document, Edit, + Idea, Gear, More, + Refresh, SensitiveHidden, SensitiveVisible, Send, + Settings, + Time, }; diff --git a/ui/desktop/src/components/ui/popover.tsx b/ui/desktop/src/components/ui/popover.tsx index 4297ca50cc..dbb592df5e 100644 --- a/ui/desktop/src/components/ui/popover.tsx +++ b/ui/desktop/src/components/ui/popover.tsx @@ -4,6 +4,8 @@ import { cn } from '../../utils'; const Popover = PopoverPrimitive.Root; +const PopoverPortal = PopoverPrimitive.Portal; + const PopoverTrigger = PopoverPrimitive.Trigger; const PopoverContent = React.forwardRef< @@ -16,7 +18,7 @@ const PopoverContent = React.forwardRef< align={align} sideOffset={sideOffset} className={cn( - 'z-50 w-72 rounded-md border bg-popover dark:bg-dark-popover text-popover-foreground dark:text-goose-text-dark shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + 'z-50 w-72 rounded-md border bg-app text-popover-foreground outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', className )} {...props} @@ -25,4 +27,4 @@ const PopoverContent = React.forwardRef< )); PopoverContent.displayName = PopoverPrimitive.Content.displayName; -export { Popover, PopoverTrigger, PopoverContent }; +export { Popover, PopoverTrigger, PopoverContent, PopoverPortal };