iteration

This commit is contained in:
Nahiyan Khan
2025-06-21 21:05:47 -04:00
parent d9ee7a56fb
commit ab03e76523
13 changed files with 363 additions and 292 deletions
+23 -41
View File
@@ -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>
);
}
+173 -138
View File
@@ -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)} />}
+64 -64
View File
@@ -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)} />
+1 -1
View File
@@ -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',
+1 -1
View File
@@ -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}
+2
View File
@@ -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);
+1 -2
View File
@@ -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: {
+10
View File
@@ -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: