mirror of
https://github.com/block/goose.git
synced 2026-07-17 12:56:20 +02:00
iteration
This commit is contained in:
@@ -40,11 +40,7 @@ export default function ChatInput({
|
||||
onStop,
|
||||
commandHistory = [],
|
||||
initialValue = '',
|
||||
setView,
|
||||
numTokens,
|
||||
droppedFiles = [],
|
||||
messages = [],
|
||||
setMessages,
|
||||
}: ChatInputProps) {
|
||||
const [_value, setValue] = useState(initialValue);
|
||||
const [displayValue, setDisplayValue] = useState(initialValue); // For immediate visual feedback
|
||||
@@ -432,11 +428,11 @@ export default function ChatInput({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col relative h-auto border rounded-2xl transition-colors animate-in fade-in slide-in-from-bottom-8 duration-500 ${
|
||||
className={`flex flex-col relative h-auto rounded-xl border mx-4 mb-4 transition-colors ${
|
||||
isFocused
|
||||
? 'border-borderProminent hover:border-borderProminent'
|
||||
: 'border-borderSubtle hover:border-borderStandard'
|
||||
} bg-bgApp z-10`}
|
||||
} z-10`}
|
||||
>
|
||||
<form onSubmit={onFormSubmit}>
|
||||
<textarea
|
||||
@@ -462,6 +458,27 @@ export default function ChatInput({
|
||||
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 placeholder:text-textPlaceholder"
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-1 absolute right-2 top-1/2 -translate-y-1/2">
|
||||
<Button type="button" shape="round" variant="ghost" onClick={handleFileSelect}>
|
||||
<Attach />
|
||||
</Button>
|
||||
|
||||
{isLoading ? (
|
||||
<Button type="button" onClick={onStop} shape="round" variant="secondary">
|
||||
<Stop />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
shape="round"
|
||||
disabled={!displayValue.trim() && pastedImages.length === 0}
|
||||
>
|
||||
<Send />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{pastedImages.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 p-2 border-t border-borderSubtle">
|
||||
{pastedImages.map((img) => (
|
||||
@@ -509,42 +526,7 @@ export default function ChatInput({
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="absolute right-1 top-1 flex items-center gap-2"></div>
|
||||
</form>
|
||||
|
||||
<div className="flex items-center transition-colors relative text-xs pb-1 px-2 gap-2">
|
||||
<div className="gap-1 flex items-center justify-between w-full">
|
||||
<BottomMenu
|
||||
setView={setView}
|
||||
numTokens={numTokens}
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
|
||||
<div className="flex items-center gap-1">
|
||||
<Button type="button" shape="round" variant="secondary" onClick={handleFileSelect}>
|
||||
<Attach />
|
||||
</Button>
|
||||
|
||||
{isLoading ? (
|
||||
<Button type="button" onClick={onStop} shape="round" variant="secondary">
|
||||
<Stop />
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="secondary"
|
||||
shape="round"
|
||||
disabled={!displayValue.trim() && pastedImages.length === 0}
|
||||
>
|
||||
<Send />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,9 @@ import {
|
||||
TextContent,
|
||||
} from '../types/message';
|
||||
import SessionsSidebar from './sessions/SessionsSideView';
|
||||
import { SidebarProvider, SidebarInset, SidebarTrigger } from './ui/sidebar';
|
||||
import { SidebarProvider, SidebarInset, SidebarTrigger, useSidebar } from './ui/sidebar';
|
||||
import MoreMenu from './more_menu/MoreMenu';
|
||||
import BottomMenu from './bottom_menu/BottomMenu';
|
||||
|
||||
// Context for sharing current model info
|
||||
const CurrentModelContext = createContext<{ model: string; mode: string } | null>(null);
|
||||
@@ -106,6 +107,34 @@ function ChatContent({
|
||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<ChatContentWithSidebar
|
||||
chat={chat}
|
||||
setChat={setChat}
|
||||
setView={setView}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
|
||||
/>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
function ChatContentWithSidebar({
|
||||
chat,
|
||||
setChat,
|
||||
setView,
|
||||
setIsGoosehintsModalOpen,
|
||||
}: {
|
||||
chat: ChatType;
|
||||
setChat: (chat: ChatType) => void;
|
||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||
setIsGoosehintsModalOpen: (isOpen: boolean) => void;
|
||||
}) {
|
||||
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
||||
|
||||
// Calculate padding based on sidebar state and macOS
|
||||
const headerPadding = safeIsMacOS ? 'pl-22' : 'pl-8';
|
||||
|
||||
const [hasMessages, setHasMessages] = useState(false);
|
||||
const [lastInteractionTime, setLastInteractionTime] = useState<number>(Date.now());
|
||||
const [showGame, setShowGame] = useState(false);
|
||||
@@ -536,153 +565,159 @@ function ChatContent({
|
||||
}
|
||||
};
|
||||
|
||||
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
|
||||
|
||||
return (
|
||||
<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 bg-background-muted">
|
||||
{/* Loader when generating recipe */}
|
||||
{isGeneratingRecipe && <LayingEggLoader />}
|
||||
|
||||
<div className="flex flex-1 w-full relative animate-fade-in">
|
||||
<SidebarProvider>
|
||||
<SessionsSidebar onSelectSession={handleSelectSession} />
|
||||
<SidebarInset>
|
||||
<div className="flex flex-col min-w-0 h-dvh">
|
||||
<div className="fixed w-full right-0 top-0 z-10 p-4 flex animate-[appear_300ms_ease-in_forwards]">
|
||||
<div className="flex w-full justify-end gap-2">
|
||||
<SidebarTrigger />
|
||||
<MoreMenu
|
||||
setView={setView || (() => {})}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen || (() => {})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SessionsSidebar onSelectSession={handleSelectSession} currentSession={chat} />
|
||||
<SidebarInset>
|
||||
<div className="flex flex-col min-w-0 h-[calc(100dvh-40px)] shadow-default bg-background-default mt-2 mr-2 mb-2 rounded-2xl animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<div className="fixed w-[16rem] left-0 top-0 z-10">
|
||||
<div
|
||||
className="flex flex-col min-w-0 flex-1 overflow-y-scroll bg-background-default relative px-6 py-16"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
className={`flex w-full py-3 ${headerPadding} pr-5 z-50 transition-[padding] duration-200 ease-in-out`}
|
||||
>
|
||||
{recipeConfig?.title && messages.length > 0 && (
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
profileInfo={
|
||||
recipeConfig.profile
|
||||
? `${recipeConfig.profile} - ${recipeConfig.mcps || 12} MCPs`
|
||||
: undefined
|
||||
}
|
||||
onChangeProfile={() => {
|
||||
// Handle profile change
|
||||
console.log('Change profile clicked');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{messages.length === 0 ? (
|
||||
<Splash
|
||||
append={append}
|
||||
activities={
|
||||
Array.isArray(recipeConfig?.activities) ? recipeConfig!.activities : null
|
||||
}
|
||||
title={recipeConfig?.title}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SearchView>
|
||||
{filteredMessages.map((message, index) => (
|
||||
<div
|
||||
key={message.id || index}
|
||||
className={`mt-4 px-6 ${isUserMessage(message) ? 'user' : 'assistant'}`}
|
||||
data-testid="message-container"
|
||||
>
|
||||
{isUserMessage(message) ? (
|
||||
<>
|
||||
{hasContextHandlerContent(message) ? (
|
||||
<ContextHandler
|
||||
messages={messages}
|
||||
messageId={message.id ?? message.created.toString()}
|
||||
chatId={chat.id}
|
||||
workingDir={window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
contextType={getContextHandlerType(message)}
|
||||
/>
|
||||
) : (
|
||||
<UserMessage message={message} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Only render GooseMessage if it's not a message invoking some context management */}
|
||||
{hasContextHandlerContent(message) ? (
|
||||
<ContextHandler
|
||||
messages={messages}
|
||||
messageId={message.id ?? message.created.toString()}
|
||||
chatId={chat.id}
|
||||
workingDir={window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
contextType={getContextHandlerType(message)}
|
||||
/>
|
||||
) : (
|
||||
<GooseMessage
|
||||
messageHistoryIndex={chat?.messageHistoryIndex}
|
||||
message={message}
|
||||
messages={messages}
|
||||
append={append}
|
||||
appendMessage={(newMessage) => {
|
||||
const updatedMessages = [...messages, newMessage];
|
||||
setMessages(updatedMessages);
|
||||
}}
|
||||
toolCallNotifications={toolCallNotifications}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</SearchView>
|
||||
|
||||
{error && (
|
||||
<div className="flex flex-col items-center justify-center p-4">
|
||||
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
|
||||
{error.message || 'Honk! Goose experienced an error while responding'}
|
||||
</div>
|
||||
<div
|
||||
className="px-3 py-2 mt-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
|
||||
onClick={async () => {
|
||||
// Find the last user message
|
||||
const lastUserMessage = messages.reduceRight(
|
||||
(found, m) => found || (m.role === 'user' ? m : null),
|
||||
null as Message | null
|
||||
);
|
||||
if (lastUserMessage) {
|
||||
append(lastUserMessage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Retry Last Message
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="block h-8" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative py-4 px-8 pt-0 z-10 animate-[fadein_400ms_ease-in_forwards]">
|
||||
{isLoading && <LoadingGoose />}
|
||||
<ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
onStop={onStopGoose}
|
||||
commandHistory={commandHistory}
|
||||
initialValue={_input || initialPrompt}
|
||||
setView={setView}
|
||||
hasMessages={hasMessages}
|
||||
numTokens={sessionTokenCount}
|
||||
droppedFiles={droppedFiles}
|
||||
messages={messages}
|
||||
setMessages={setMessages}
|
||||
<SidebarTrigger />
|
||||
<MoreMenu
|
||||
setView={setView || (() => {})}
|
||||
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen || (() => {})}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
|
||||
<div
|
||||
className="flex flex-col min-w-0 flex-1 overflow-y-scroll relative pl-6 pr-4 pb-16 pt-12"
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
>
|
||||
{recipeConfig?.title && messages.length > 0 && (
|
||||
<AgentHeader
|
||||
title={recipeConfig.title}
|
||||
profileInfo={
|
||||
recipeConfig.profile
|
||||
? `${recipeConfig.profile} - ${recipeConfig.mcps || 12} MCPs`
|
||||
: undefined
|
||||
}
|
||||
onChangeProfile={() => {
|
||||
// Handle profile change
|
||||
console.log('Change profile clicked');
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{messages.length === 0 ? (
|
||||
<Splash
|
||||
append={append}
|
||||
activities={
|
||||
Array.isArray(recipeConfig?.activities) ? recipeConfig!.activities : null
|
||||
}
|
||||
title={recipeConfig?.title}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<SearchView>
|
||||
{filteredMessages.map((message, index) => (
|
||||
<div
|
||||
key={message.id || index}
|
||||
className={`mt-4 px-6 ${isUserMessage(message) ? 'user' : 'assistant'}`}
|
||||
data-testid="message-container"
|
||||
>
|
||||
{isUserMessage(message) ? (
|
||||
<>
|
||||
{hasContextHandlerContent(message) ? (
|
||||
<ContextHandler
|
||||
messages={messages}
|
||||
messageId={message.id ?? message.created.toString()}
|
||||
chatId={chat.id}
|
||||
workingDir={window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
contextType={getContextHandlerType(message)}
|
||||
/>
|
||||
) : (
|
||||
<UserMessage message={message} />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{/* Only render GooseMessage if it's not a message invoking some context management */}
|
||||
{hasContextHandlerContent(message) ? (
|
||||
<ContextHandler
|
||||
messages={messages}
|
||||
messageId={message.id ?? message.created.toString()}
|
||||
chatId={chat.id}
|
||||
workingDir={window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
contextType={getContextHandlerType(message)}
|
||||
/>
|
||||
) : (
|
||||
<GooseMessage
|
||||
messageHistoryIndex={chat?.messageHistoryIndex}
|
||||
message={message}
|
||||
messages={messages}
|
||||
append={append}
|
||||
appendMessage={(newMessage) => {
|
||||
const updatedMessages = [...messages, newMessage];
|
||||
setMessages(updatedMessages);
|
||||
}}
|
||||
toolCallNotifications={toolCallNotifications}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</SearchView>
|
||||
|
||||
{error && (
|
||||
<div className="flex flex-col items-center justify-center p-4">
|
||||
<div className="text-red-700 dark:text-red-300 bg-red-400/50 p-3 rounded-lg mb-2">
|
||||
{error.message || 'Honk! Goose experienced an error while responding'}
|
||||
</div>
|
||||
<div
|
||||
className="px-3 py-2 mt-2 text-center whitespace-nowrap cursor-pointer text-textStandard border border-borderSubtle hover:bg-bgSubtle rounded-full inline-block transition-all duration-150"
|
||||
onClick={async () => {
|
||||
// Find the last user message
|
||||
const lastUserMessage = messages.reduceRight(
|
||||
(found, m) => found || (m.role === 'user' ? m : null),
|
||||
null as Message | null
|
||||
);
|
||||
if (lastUserMessage) {
|
||||
append(lastUserMessage);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Retry Last Message
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="block h-8" />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative z-10 animate-[fadein_400ms_ease-in_forwards]">
|
||||
{isLoading && <LoadingGoose />}
|
||||
<ChatInput
|
||||
handleSubmit={handleSubmit}
|
||||
isLoading={isLoading}
|
||||
onStop={onStopGoose}
|
||||
commandHistory={commandHistory}
|
||||
initialValue={_input || initialPrompt}
|
||||
setView={setView}
|
||||
hasMessages={hasMessages}
|
||||
numTokens={sessionTokenCount}
|
||||
droppedFiles={droppedFiles}
|
||||
messages={messages}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<BottomMenu
|
||||
setView={setView}
|
||||
numTokens={sessionTokenCount}
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</SidebarInset>
|
||||
</div>
|
||||
|
||||
{showGame && <FlappyGoose onClose={() => setShowGame(false)} />}
|
||||
|
||||
@@ -26,47 +26,49 @@ export default function Splash({ append, activities, title }: SplashProps) {
|
||||
? [...pills.slice(0, messagePillIndex), ...pills.slice(messagePillIndex + 1)]
|
||||
: pills;
|
||||
|
||||
const [timeOfDay, setTimeOfDay] = useState<'morning' | 'afternoon' | 'night'>('morning');
|
||||
const [selectedGreeting, setSelectedGreeting] = useState<{
|
||||
prefix: string;
|
||||
message: string;
|
||||
} | null>(null);
|
||||
|
||||
// Update time of day, current time, and date based on current time
|
||||
// Select a random greeting on component mount
|
||||
useEffect(() => {
|
||||
const updateTime = () => {
|
||||
const now = new Date();
|
||||
const hour = now.getHours();
|
||||
const prefixes = ['Hello.', 'Welcome.', 'Greetings.', 'Welcome back.', 'Hello there.'];
|
||||
|
||||
// Update time of day
|
||||
if (hour >= 5 && hour < 12) {
|
||||
setTimeOfDay('morning');
|
||||
} else if (hour >= 12 && hour < 18) {
|
||||
setTimeOfDay('afternoon');
|
||||
} else {
|
||||
setTimeOfDay('night');
|
||||
}
|
||||
};
|
||||
const messages = [
|
||||
' Ready to get started?',
|
||||
' What would you like to work on?',
|
||||
' Ready to build something amazing?',
|
||||
' What would you like to explore?',
|
||||
" What's on your mind?",
|
||||
' What shall we create today?',
|
||||
' What project needs attention?',
|
||||
' What would you like to tackle?',
|
||||
' What would you like to explore?',
|
||||
' What needs to be done?',
|
||||
" What's the plan for today?",
|
||||
' Ready to create something great?',
|
||||
' What can be built today?',
|
||||
" What's the next challenge?",
|
||||
' What progress can be made?',
|
||||
' What would you like to accomplish?',
|
||||
' What task awaits?',
|
||||
" What's the mission today?",
|
||||
' What can be achieved?',
|
||||
' What project is ready to begin?',
|
||||
];
|
||||
|
||||
updateTime();
|
||||
const interval = setInterval(updateTime, 1000);
|
||||
return () => clearInterval(interval);
|
||||
const randomPrefixIndex = Math.floor(Math.random() * prefixes.length);
|
||||
const randomMessageIndex = Math.floor(Math.random() * messages.length);
|
||||
|
||||
setSelectedGreeting({
|
||||
prefix: prefixes[randomPrefixIndex],
|
||||
message: messages[randomMessageIndex],
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getGreeting = () => {
|
||||
switch (timeOfDay) {
|
||||
case 'morning':
|
||||
return {
|
||||
prefix: 'Morning.',
|
||||
message: " Let's get things done.",
|
||||
};
|
||||
case 'afternoon':
|
||||
return {
|
||||
prefix: 'Afternoon.',
|
||||
message: ' Keep the momentum going.',
|
||||
};
|
||||
case 'night':
|
||||
return {
|
||||
prefix: 'Evening.',
|
||||
message: ' Time to wrap things up.',
|
||||
};
|
||||
}
|
||||
return selectedGreeting || { prefix: 'Hello.', message: ' How can I help you today?' };
|
||||
};
|
||||
|
||||
const greeting = getGreeting();
|
||||
@@ -91,43 +93,41 @@ export default function Splash({ append, activities, title }: SplashProps) {
|
||||
)}
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="h-full flex flex-col pb-12">
|
||||
<div className="px-2">
|
||||
{/* <div className="relative text-textStandard mb-12">
|
||||
{/* <div className="relative text-textStandard mb-12">
|
||||
<div className="w-min animate-[flyin_2s_var(--spring-easing)_forwards]">
|
||||
<GooseLogo />
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
<div className="flex flex-col mt-2 mb-4 animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
<h1 className="text-text-prominent text-4xl font-light min-h-[4rem]">
|
||||
<span>{greeting.prefix}</span>
|
||||
<div className="text-text-muted inline">{greeting.message}</div>
|
||||
</h1>
|
||||
<div className="flex flex-col mb-8">
|
||||
<h1 className="text-text-prominent text-4xl font-light">
|
||||
<span>{greeting.prefix}</span>
|
||||
</h1>
|
||||
<div className="text-text-muted text-4xl font-light inline">{greeting.message}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{messagePill && (
|
||||
<div className="mb-6 p-4 rounded-lg border animate-[fadein_500ms_ease-in_forwards]">
|
||||
{messagePill.replace(/^message:/i, '').trim()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-4 animate-[fadein_500ms_ease-in_forwards]">
|
||||
{remainingPills.map((content, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
onClick={() => append(content)}
|
||||
title={content.length > 100 ? content : undefined}
|
||||
className="cursor-pointer px-6 w-[256px]"
|
||||
>
|
||||
{content.length > 100 ? content.slice(0, 100) + '...' : content}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col">
|
||||
{messagePill && (
|
||||
<div className="mb-6 p-4 bg-bgSubtle rounded-lg border border-borderStandard animate-[fadein_500ms_ease-in_forwards]">
|
||||
{messagePill.replace(/^message:/i, '').trim()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-wrap gap-4 animate-[fadein_500ms_ease-in_forwards]">
|
||||
{remainingPills.map((content, index) => (
|
||||
<Card
|
||||
key={index}
|
||||
onClick={() => append(content)}
|
||||
title={content.length > 100 ? content : undefined}
|
||||
className="cursor-pointer px-6 w-[256px]"
|
||||
>
|
||||
{content.length > 100 ? content.slice(0, 100) + '...' : content}
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="animate-[fadein_500ms_ease-in_forwards]">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
<div className="animate-[fadein_500ms_ease-in_forwards]">
|
||||
<SessionInsights />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -202,32 +202,30 @@ export default function BottomMenu({
|
||||
}, [isModelMenuOpen]);
|
||||
|
||||
return (
|
||||
<div className="flex justify-between items-center transition-colors text-textSubtle relative text-xs align-middle">
|
||||
<div className="flex items-center pl-2">
|
||||
{/* Tool and Token count */}
|
||||
{<BottomMenuAlertPopover alerts={alerts} />}
|
||||
<div className="flex w-full justify-end items-center pb-2 pr-3 transition-colors relative text-xs align-middle animate-in fade-in slide-in-from-bottom-8 duration-500">
|
||||
{/* Tool and Token count */}
|
||||
{<BottomMenuAlertPopover alerts={alerts} />}
|
||||
|
||||
{/* Model Selector Dropdown */}
|
||||
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
||||
{/* Model Selector Dropdown */}
|
||||
<ModelsBottomBar dropdownRef={dropdownRef} setView={setView} />
|
||||
|
||||
{/* Separator */}
|
||||
<div className="w-[1px] h-4 bg-borderSubtle mx-2" />
|
||||
{/* Separator */}
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
|
||||
{/* Goose Mode Selector Dropdown */}
|
||||
<BottomMenuModeSelection setView={setView} />
|
||||
{/* Goose Mode Selector Dropdown */}
|
||||
<BottomMenuModeSelection setView={setView} />
|
||||
|
||||
{/* Summarize Context Button - ADD THIS */}
|
||||
{messages.length > 0 && (
|
||||
<>
|
||||
<div className="w-[1px] h-4 bg-borderSubtle mx-2" />
|
||||
<ManualSummarizeButton
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{/* Summarize Context Button - ADD THIS */}
|
||||
{messages.length > 0 && (
|
||||
<>
|
||||
<div className="w-1 h-1 rounded-full bg-background-accent/50 mx-2" />
|
||||
<ManualSummarizeButton
|
||||
messages={messages}
|
||||
isLoading={isLoading}
|
||||
setMessages={setMessages}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { all_goose_modes, ModeSelectionItem } from '../settings/mode/ModeSelecti
|
||||
import { useConfig } from '../ConfigContext';
|
||||
import { View, ViewOptions } from '../../App';
|
||||
import { Orbit } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
interface BottomMenuModeSelectionProps {
|
||||
setView: (view: View, viewOptions?: ViewOptions) => void;
|
||||
@@ -85,13 +86,12 @@ export const BottomMenuModeSelection = ({ setView }: BottomMenuModeSelectionProp
|
||||
|
||||
return (
|
||||
<div className="relative flex items-center" ref={gooseModeDropdownRef}>
|
||||
<button
|
||||
className="flex items-center justify-center text-textSubtle hover:text-textStandard h-6 [&_svg]:size-4"
|
||||
<span
|
||||
className="flex items-center cursor-pointer [&_svg]:size-4 text-text-default/70 hover:text-text-default hover:scale-100 hover:bg-transparent text-xs"
|
||||
onClick={() => setIsGooseModeMenuOpen(!isGooseModeMenuOpen)}
|
||||
>
|
||||
<span className="pr-1.5">{getValueByKey(gooseMode).toLowerCase()}</span>
|
||||
<Orbit />
|
||||
</button>
|
||||
{getValueByKey(gooseMode).toLowerCase()}
|
||||
</span>
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{isGooseModeMenuOpen && (
|
||||
|
||||
@@ -238,7 +238,13 @@ export default function MoreMenu({
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button data-testid="more-options-button" className="z-[100] no-drag" shape="round">
|
||||
<Button
|
||||
data-testid="more-options-button"
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="z-[100] no-drag"
|
||||
shape="round"
|
||||
>
|
||||
<Settings />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
||||
@@ -19,6 +19,12 @@ import { cn } from '../../utils';
|
||||
|
||||
interface SessionsSidebarProps {
|
||||
onSelectSession: (sessionId: string) => void;
|
||||
currentSession?: {
|
||||
id: string;
|
||||
title: string;
|
||||
messageHistoryIndex: number;
|
||||
messages: any[];
|
||||
};
|
||||
}
|
||||
|
||||
interface GroupedSessions {
|
||||
@@ -27,7 +33,7 @@ interface GroupedSessions {
|
||||
older: { [key: string]: Session[] };
|
||||
}
|
||||
|
||||
const SessionsSidebar: React.FC<SessionsSidebarProps> = ({ onSelectSession }) => {
|
||||
const SessionsSidebar: React.FC<SessionsSidebarProps> = ({ onSelectSession, currentSession }) => {
|
||||
const [sessions, setSessions] = useState<Session[]>([]);
|
||||
// @ts-expect-error
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
@@ -143,11 +149,9 @@ const SessionsSidebar: React.FC<SessionsSidebarProps> = ({ onSelectSession }) =>
|
||||
<Collapsible defaultOpen={isFirstTwoGroups} className="group/collapsible">
|
||||
<SidebarGroup>
|
||||
<CollapsibleTrigger className="w-full">
|
||||
<SidebarGroupLabel className="flex cursor-pointer items-center justify-between h-12">
|
||||
<SidebarGroupLabel className="flex cursor-pointer items-center justify-between text-text-muted hover:text-text-default h-12">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<span className="opacity-100 transition-all duration-200 text-xs text-text-muted">
|
||||
{title}
|
||||
</span>
|
||||
<span className="opacity-100 transition-all duration-200 text-xs">{title}</span>
|
||||
</div>
|
||||
<ChevronDown className="size-4 text-text-muted flex-shrink-0 opacity-100 transition-all duration-200 group-data-[state=open]/collapsible:rotate-180" />
|
||||
</SidebarGroupLabel>
|
||||
@@ -182,18 +186,52 @@ const SessionsSidebar: React.FC<SessionsSidebarProps> = ({ onSelectSession }) =>
|
||||
);
|
||||
};
|
||||
|
||||
const renderCurrentSession = () => {
|
||||
if (!currentSession || currentSession.messages.length === 0) return null;
|
||||
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel className="flex items-center h-12">
|
||||
<span className="opacity-100 transition-all duration-200 text-xs text-text-muted">
|
||||
Current Session
|
||||
</span>
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu className="mb-2 ml-2 pl-2 border-l border-border-default">
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
asChild
|
||||
className="cursor-pointer w-52 transition-all duration-150 bg-background-muted rounded-2xl text-text-default h-fit flex items-start"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm w-48 px-2 truncate -mb-1 text-ellipsis">
|
||||
{currentSession.title || currentSession.id}
|
||||
</div>
|
||||
<div className="text-xs w-48 px-2 truncate flex items-center gap-2 text-ellipsis">
|
||||
<Folder className="size-4" />
|
||||
{window.appConfig.get('GOOSE_WORKING_DIR') as string}
|
||||
</div>
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Sidebar
|
||||
collapsible="offcanvas"
|
||||
variant="inset"
|
||||
side="left"
|
||||
className={cn(
|
||||
'border-r transition-all duration-500 ease-out',
|
||||
'transition-all duration-500 ease-out',
|
||||
isVisible ? 'translate-x-0 opacity-100' : '-translate-x-full opacity-0'
|
||||
)}
|
||||
>
|
||||
<SidebarHeader>
|
||||
<div className="p-1 pt-16 opacity-100 transition-all duration-200">
|
||||
<div className="p-1 pt-12 opacity-100 transition-all duration-200">
|
||||
<div className="relative flex flex-row items-center gap-2">
|
||||
<Search className="absolute top-2.5 left-2.5 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
@@ -234,6 +272,7 @@ const SessionsSidebar: React.FC<SessionsSidebarProps> = ({ onSelectSession }) =>
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<ScrollArea className="h-full">
|
||||
{renderCurrentSession()}
|
||||
{(() => {
|
||||
let groupIndex = 0;
|
||||
const groups = [
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../../
|
||||
import Modal from '../../../Modal';
|
||||
import { useCurrentModelInfo } from '../../../ChatView';
|
||||
import { useConfig } from '../../../ConfigContext';
|
||||
import { Button } from '../../../ui/button';
|
||||
|
||||
interface ModelsBottomBarProps {
|
||||
dropdownRef: React.RefObject<HTMLDivElement>;
|
||||
@@ -43,7 +44,10 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
|
||||
}, [read]);
|
||||
|
||||
// Determine which model to display - activeModel takes priority when lead/worker is active
|
||||
const displayModel = (isLeadWorkerActive && currentModelInfo?.model) ? currentModelInfo.model : (currentModel || 'Select Model');
|
||||
const displayModel =
|
||||
isLeadWorkerActive && currentModelInfo?.model
|
||||
? currentModelInfo.model
|
||||
: currentModel || 'Select Model';
|
||||
const modelMode = currentModelInfo?.mode;
|
||||
|
||||
// Update display provider when current provider changes
|
||||
@@ -102,13 +106,11 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
|
||||
<TooltipTrigger asChild>
|
||||
<span
|
||||
ref={modelRef}
|
||||
className="truncate max-w-[130px] md:max-w-[200px] lg:max-w-[360px] min-w-0 block"
|
||||
className="truncate text-text-default/70 hover:text-text-default hover:scale-100 hover:bg-transparent text-xs max-w-[130px] md:max-w-[200px] lg:max-w-[360px] min-w-0 block"
|
||||
>
|
||||
{displayModel}
|
||||
{isLeadWorkerActive && modelMode && (
|
||||
<span className="ml-1 text-[10px] opacity-60">
|
||||
({modelMode})
|
||||
</span>
|
||||
<span className="ml-1 text-[10px] opacity-60">({modelMode})</span>
|
||||
)}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
@@ -116,9 +118,7 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
|
||||
<TooltipContent className="max-w-96 overflow-auto scrollbar-thin" side="top">
|
||||
{displayModel}
|
||||
{isLeadWorkerActive && modelMode && (
|
||||
<span className="ml-1 text-[10px] opacity-60">
|
||||
({modelMode})
|
||||
</span>
|
||||
<span className="ml-1 text-[10px] opacity-60">({modelMode})</span>
|
||||
)}
|
||||
</TooltipContent>
|
||||
)}
|
||||
@@ -128,7 +128,7 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
|
||||
|
||||
{/* Dropdown Menu */}
|
||||
{isModelMenuOpen && (
|
||||
<div className="absolute bottom-[24px] right-[-55px] w-[300px] bg-bgApp rounded-lg border border-borderSubtle">
|
||||
<div className="absolute bottom-[24px] right-[-55px] w-[300px] bg-bgApp rounded-lg border">
|
||||
<div className="">
|
||||
<div className="text-sm text-textProminent mt-2 ml-2">Current:</div>
|
||||
<div className="flex items-center justify-between text-sm ml-2">
|
||||
@@ -164,7 +164,7 @@ export default function ModelsBottomBar({ dropdownRef, setView }: ModelsBottomBa
|
||||
{isAddModelModalOpen ? (
|
||||
<AddModelModal setView={setView} onClose={() => setIsAddModelModalOpen(false)} />
|
||||
) : null}
|
||||
|
||||
|
||||
{isLeadWorkerModalOpen ? (
|
||||
<Modal onClose={() => setIsLeadWorkerModalOpen(false)}>
|
||||
<LeadWorkerSettings onClose={() => setIsLeadWorkerModalOpen(false)} />
|
||||
|
||||
@@ -11,7 +11,7 @@ const buttonVariants = cva(
|
||||
default: 'bg-background-accent text-text-on-accent hover:bg-background-accent/90 shadow-xs',
|
||||
destructive:
|
||||
'bg-background-danger text-white hover:bg-background-danger/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-background-danger/60 shadow-xs',
|
||||
outline: 'bg-background-default border-none shadow-default',
|
||||
outline: 'border hover:bg-background-default',
|
||||
secondary: 'bg-background-muted text-text-default hover:bg-background-muted/80 shadow-xs',
|
||||
ghost: 'hover:bg-background-muted dark:hover:bg-background-muted/50',
|
||||
link: 'text-text-accent underline-offset-4 hover:underline',
|
||||
|
||||
@@ -8,7 +8,7 @@ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
'flex h-9 w-full rounded-xl border focus:border-border-strong hover:border-border-strong bg-transparent px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-textPlaceholder focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
'flex h-9 w-full rounded-xl border focus:border-border-strong hover:border-border-strong bg-background-default px-3 py-1 text-base transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-textPlaceholder focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -247,6 +247,8 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
|
||||
data-sidebar="trigger"
|
||||
data-slot="sidebar-trigger"
|
||||
shape="round"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn(className)}
|
||||
onClick={(event) => {
|
||||
onClick?.(event);
|
||||
|
||||
@@ -437,7 +437,7 @@ const createChat = async (
|
||||
|
||||
const mainWindow = new BrowserWindow({
|
||||
titleBarStyle: process.platform === 'darwin' ? 'hidden' : 'default',
|
||||
trafficLightPosition: process.platform === 'darwin' ? { x: 20, y: 26 } : undefined,
|
||||
trafficLightPosition: process.platform === 'darwin' ? { x: 20, y: 21 } : undefined,
|
||||
vibrancy: process.platform === 'darwin' ? 'window' : undefined,
|
||||
frame: process.platform === 'darwin' ? false : true,
|
||||
x: mainWindowState.x,
|
||||
@@ -446,7 +446,6 @@ const createChat = async (
|
||||
height: mainWindowState.height,
|
||||
minWidth: 650,
|
||||
resizable: true,
|
||||
transparent: false,
|
||||
useContentSize: true,
|
||||
icon: path.join(__dirname, '../images/icon'),
|
||||
webPreferences: {
|
||||
|
||||
@@ -189,6 +189,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src:
|
||||
url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff2/CashSans-Light.woff2)
|
||||
format('woff2'),
|
||||
url(https://cash-f.squarecdn.com/static/fonts/cashsans/woff/CashSans-Light.woff) format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Cash Sans';
|
||||
src:
|
||||
|
||||
Reference in New Issue
Block a user