diff --git a/ui/desktop/src/components/ChatInput.tsx b/ui/desktop/src/components/ChatInput.tsx index 18a6f5fb02..aa701752b9 100644 --- a/ui/desktop/src/components/ChatInput.tsx +++ b/ui/desktop/src/components/ChatInput.tsx @@ -428,7 +428,7 @@ export default function ChatInput({ return (
{isLoading ? ( - ) : ( + + +

Customize instructions

+
+ + )} + + {recipeConfig ? ( + <> + + + + + +

View the recipe you're using

+
+
+ + + + + + +

Save this recipe for reuse

+
+
+ + ) : ( + + + + + +

Make a custom agent you can share or reuse

+
+
+ )} + + {/* */}
+ {/* Session Insights at the top */} + {messages.length === 0 && ( +
+ +
+ )} + {recipeConfig?.title && messages.length > 0 && ( )} {messages.length === 0 ? ( - + <> + {/* Empty search view when no messages */} + ) : ( <> @@ -741,6 +857,20 @@ function ChatContentWithSidebar({
{isLoading && } + + {/* Splash component at the bottom when no messages + {messages.length === 0 && ( +
+ +
+ )} */} + void; @@ -96,14 +96,64 @@ const AppSidebar: React.FC = ({ setIsGoosehintsModalOpen, setView return ( <> - {/* Action Buttons */} -
+ {/* +
+ + codename goose +
+
*/} + + {/* Menu */} +
+ + + + + +

Start a new session in the current directory

+
+
+ + + + + + +

Start a new session in a different directory

+
+
+ {setView && ( - - -

Customize instructions

-
-
- )} - - {recipeConfig ? ( - <> - - - - - -

View the recipe you're using

-
-
- - - - - - -

Save this recipe for reuse

-
-
- - ) : ( - - - - - -

Make a custom agent you can share or reuse

-
-
- )} {setView && ( @@ -247,12 +208,12 @@ const AppSidebar: React.FC = ({ setIsGoosehintsModalOpen, setView )} - {/* {setView && ( + {setView && ( - - -

Start a new session in the current directory

-
-
+
+ + codename goose +
- - - - - -

Start a new session in a different directory

-
-
+
+
+ + + + + + + +

Join our Discord

+
+
- {/* */} + + + + + + + +

Follow us on LinkedIn

+
+
- + + + + + + + +

Watch on YouTube

+
+
+
diff --git a/ui/desktop/src/components/Splash.tsx b/ui/desktop/src/components/Splash.tsx index 78d6e80db4..3270578442 100644 --- a/ui/desktop/src/components/Splash.tsx +++ b/ui/desktop/src/components/Splash.tsx @@ -2,7 +2,6 @@ import { useTextAnimator } from '../hooks/use-text-animator'; import { Card } from './ui/card'; import { useState, useEffect } from 'react'; import { gsap } from 'gsap'; -import { SessionInsights } from './sessions/SessionsInsights'; // Register GSAP plugins gsap.registerPlugin(); @@ -81,9 +80,9 @@ export default function Splash({ append, activities, title }: SplashProps) { }, []); return ( -
+
{title && ( -
+
Agent{' '} @@ -91,45 +90,33 @@ export default function Splash({ append, activities, title }: SplashProps) {
)} -
-
- {/*
-
- -
-
*/} -
-

- {greeting.prefix} -

-
{greeting.message}
+ {/* Compact greeting section */} +
+

+ {greeting.prefix} + {/* {greeting.message} */} +

+
+ +
+ {messagePill && ( +
+ {messagePill.replace(/^message:/i, '').trim()}
+ )} -
- {messagePill && ( -
- {messagePill.replace(/^message:/i, '').trim()} -
- )} - -
- {remainingPills.map((content, index) => ( - append(content)} - title={content.length > 100 ? content : undefined} - className="cursor-pointer px-6 w-[256px]" - > - {content.length > 100 ? content.slice(0, 100) + '...' : content} - - ))} -
- -
- -
-
+
+ {remainingPills.map((content, index) => ( + append(content)} + title={content.length > 100 ? content : undefined} + className="cursor-pointer px-4 py-2 w-[200px]" + > + {content.length > 100 ? content.slice(0, 100) + '...' : content} + + ))}
diff --git a/ui/desktop/src/components/bottom_menu/BottomMenu.tsx b/ui/desktop/src/components/bottom_menu/BottomMenu.tsx index 3bab14eb9f..8e0034c353 100644 --- a/ui/desktop/src/components/bottom_menu/BottomMenu.tsx +++ b/ui/desktop/src/components/bottom_menu/BottomMenu.tsx @@ -9,6 +9,7 @@ import { useConfig } from '../ConfigContext'; import { useModelAndProvider } from '../ModelAndProviderContext'; import { Message } from '../../types/message'; import { ManualSummarizeButton } from '../context_management/ManualSummaryButton'; +import { DirSwitcher } from './DirSwitcher'; 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 @@ -202,30 +203,36 @@ export default function BottomMenu({ }, [isModelMenuOpen]); return ( -
- {/* Tool and Token count */} - {} +
+
+ 0} /> +
- {/* Model Selector Dropdown */} - +
+ {/* Tool and Token count */} + {} - {/* Separator */} -
+ {/* Model Selector Dropdown */} + - {/* Goose Mode Selector Dropdown */} - + {/* Separator */} +
- {/* Summarize Context Button - ADD THIS */} - {messages.length > 0 && ( - <> -
- - - )} + {/* Goose Mode Selector Dropdown */} + + + {/* Summarize Context Button - ADD THIS */} + {messages.length > 0 && ( + <> +
+ + + )} +
); } diff --git a/ui/desktop/src/components/bottom_menu/DirSwitcher.tsx b/ui/desktop/src/components/bottom_menu/DirSwitcher.tsx new file mode 100644 index 0000000000..0bec2fb8f5 --- /dev/null +++ b/ui/desktop/src/components/bottom_menu/DirSwitcher.tsx @@ -0,0 +1,44 @@ +import React, { useState } from 'react'; +import { Document } from '../icons'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/Tooltip'; + +interface DirSwitcherProps { + hasMessages?: boolean; + className?: string; +} + +export const DirSwitcher: React.FC = ({ + hasMessages = false, + className = '', +}) => { + const [isTooltipOpen, setIsTooltipOpen] = useState(false); + + const handleDirectoryChange = async () => { + if (hasMessages) { + window.electron.directoryChooser(); + } else { + window.electron.directoryChooser(true); + } + }; + + return ( + + + + + + + {window.appConfig.get('GOOSE_WORKING_DIR') as string} + + + + ); +}; diff --git a/ui/desktop/src/components/icons/Discord.tsx b/ui/desktop/src/components/icons/Discord.tsx new file mode 100644 index 0000000000..b26397c73a --- /dev/null +++ b/ui/desktop/src/components/icons/Discord.tsx @@ -0,0 +1,18 @@ +export default function Discord({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/LinkedIn.tsx b/ui/desktop/src/components/icons/LinkedIn.tsx new file mode 100644 index 0000000000..a2be6b9674 --- /dev/null +++ b/ui/desktop/src/components/icons/LinkedIn.tsx @@ -0,0 +1,18 @@ +export default function LinkedIn({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/Youtube.tsx b/ui/desktop/src/components/icons/Youtube.tsx new file mode 100644 index 0000000000..9d2d231558 --- /dev/null +++ b/ui/desktop/src/components/icons/Youtube.tsx @@ -0,0 +1,18 @@ +export default function Youtube({ className = '' }) { + return ( + + ); +} diff --git a/ui/desktop/src/components/icons/index.tsx b/ui/desktop/src/components/icons/index.tsx index 72a3b6338e..22354b47ad 100644 --- a/ui/desktop/src/components/icons/index.tsx +++ b/ui/desktop/src/components/icons/index.tsx @@ -9,9 +9,11 @@ import ChevronUp from './ChevronUp'; import { ChevronRight } from './ChevronRight'; import Close from './Close'; import Copy from './Copy'; +import Discord from './Discord'; import Document from './Document'; import Edit from './Edit'; import Idea from './Idea'; +import LinkedIn from './LinkedIn'; import More from './More'; import Refresh from './Refresh'; import SensitiveHidden from './SensitiveHidden'; @@ -20,6 +22,7 @@ import Send from './Send'; import Settings from './Settings'; import Time from './Time'; import { Gear } from './Gear'; +import Youtube from './Youtube'; export { ArrowDown, @@ -33,10 +36,12 @@ export { ChevronUp, Close, Copy, + Discord, Document, Edit, Idea, Gear, + LinkedIn, More, Refresh, SensitiveHidden, @@ -44,4 +49,5 @@ export { Send, Settings, Time, + Youtube, }; diff --git a/ui/desktop/src/components/sessions/SessionsInsights.tsx b/ui/desktop/src/components/sessions/SessionsInsights.tsx index 350f884216..b917582a9d 100644 --- a/ui/desktop/src/components/sessions/SessionsInsights.tsx +++ b/ui/desktop/src/components/sessions/SessionsInsights.tsx @@ -11,6 +11,36 @@ interface SessionInsights { totalTokens: number; } +// Greeting component +function GreetingCard() { + const [selectedGreeting, setSelectedGreeting] = useState<{ + prefix: string; + } | null>(null); + + // Select a random greeting on component mount + useEffect(() => { + const prefixes = ['Hello.', 'Welcome.', 'Greetings.', 'Welcome back.', 'Hello there.']; + const randomPrefixIndex = Math.floor(Math.random() * prefixes.length); + + setSelectedGreeting({ + prefix: prefixes[randomPrefixIndex], + }); + }, []); + + const greeting = selectedGreeting || { prefix: 'Hello.', message: ' How can I help you today?' }; + + return ( + + +

+ {greeting.prefix} +

+ {/*

{greeting.message}

*/} +
+
+ ); +} + export function SessionInsights() { const [insights, setInsights] = useState(null); const [error, setError] = useState(null); @@ -55,45 +85,49 @@ export function SessionInsights() { } return ( -
- {/* Total Sessions Card */} - - -
-

- {insights?.totalSessions} -

- Total sessions -
-
-
+ <> +
+ +
+
+ {/* Total Sessions Card */} + + +
+

+ {insights?.totalSessions} +

+ Total sessions +
+
+
- {/* Average Duration Card */} - - -
-

- {insights?.avgSessionDuration?.toFixed(1)}m -

- Avg. duration -
-
-
+ {/* Average Duration Card */} + + +
+

+ {insights?.avgSessionDuration?.toFixed(1)}m +

+ Avg. duration +
+
+
- {/* Total Tokens Card */} - - -
-

- {insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''} -

- Total tokens (millions) -
-
-
+ {/* Total Tokens Card */} + + +
+

+ {insights?.totalTokens ? `${(insights.totalTokens / 1000000).toFixed(2)}M` : ''} +

+ Total tokens (millions) +
+
+
- {/* Activity Heatmap Card */} - {/* + {/* Activity Heatmap Card */} + {/* Activity Heatmap @@ -102,25 +136,26 @@ export function SessionInsights() { */} - {/* Most Active Directories Card */} - - - - Active directories - -
- {insights.mostActiveDirs.map(([dir, count], index) => ( -
-
- - {dir} + {/* Most Active Directories Card */} + + + + Active directories + +
+ {insights.mostActiveDirs.map(([dir, count], index) => ( +
+
+ + {dir} +
+ {count} sessions
- {count} sessions -
- ))} -
- - -
+ ))} +
+
+
+
+ ); } diff --git a/ui/desktop/src/main.ts b/ui/desktop/src/main.ts index b60e4bbd7f..8a53c3e287 100644 --- a/ui/desktop/src/main.ts +++ b/ui/desktop/src/main.ts @@ -11,6 +11,7 @@ import { Tray, App, globalShortcut, + shell, } from 'electron'; import type { OpenDialogReturnValue } from 'electron'; import { Buffer } from 'node:buffer'; @@ -528,12 +529,18 @@ const createChat = async ( mainWindow.webContents.setWindowOpenHandler(({ url }) => { // Open all links in external browser if (url.startsWith('http:') || url.startsWith('https:')) { - electron.shell.openExternal(url); + shell.openExternal(url); return { action: 'deny' }; } return { action: 'allow' }; }); + // Handle new-window events (alternative approach for external links) + mainWindow.webContents.on('new-window', (event, url) => { + event.preventDefault(); + shell.openExternal(url); + }); + // Load the index.html of the app. let queryParams = ''; if (query) { @@ -1756,18 +1763,10 @@ app.whenReady().then(async () => { return; } - // On macOS, use the 'open' command with Chrome - if (process.platform === 'darwin') { - spawn('open', ['-a', 'Google Chrome', url]); - } else if (process.platform === 'win32') { - // On Windows, start is built-in command of cmd.exe - spawn('cmd.exe', ['/c', 'start', '', 'chrome', url]); - } else { - // On Linux, use xdg-open with chrome - spawn('xdg-open', [url]); - } + // Use shell.openExternal to open URL in user's default browser + shell.openExternal(url); } catch (error) { - console.error('Error opening URL in Chrome:', error); + console.error('Error opening URL in browser:', error); } }); diff --git a/ui/desktop/src/styles/main.css b/ui/desktop/src/styles/main.css index 57dd111eff..abc5f1609d 100644 --- a/ui/desktop/src/styles/main.css +++ b/ui/desktop/src/styles/main.css @@ -12,7 +12,7 @@ --color-black: #000000; --color-neutral-100: #e8eaf2; - --color-neutral-200: #ccd2dd; + --color-neutral-200: #d4dae2; --color-neutral-300: #a1a7b0; --color-neutral-400: #6e747e; --color-neutral-500: #32353b;