Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 19 additions & 29 deletions ui/desktop/src/components/Layout/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react';
import { Outlet, useNavigate, useLocation } from 'react-router-dom';
import AppSidebar from '../GooseSidebar/AppSidebar';
import GlobalBackground from '../GlobalBackground';
import { View, ViewOptions } from '../../App';
import { AppWindowMac, AppWindow } from 'lucide-react';
import { Button } from '../ui/button';
import { Sidebar, SidebarInset, SidebarProvider, SidebarTrigger, useSidebar } from '../ui/sidebar';
import { SidebarProvider, useSidebar } from '../ui/sidebar';
import GlobalBackground from '../GlobalBackground';
import PillSideNav from '../PillSideNav';

interface AppLayoutProps {
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
Expand All @@ -18,10 +18,6 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
const safeIsMacOS = (window?.electron?.platform || 'darwin') === 'darwin';
const { isMobile, openMobile } = useSidebar();

// Calculate padding based on sidebar state and macOS
const headerPadding = safeIsMacOS ? 'pl-21' : 'pl-4';
// const headerPadding = '';

// Hide buttons when mobile sheet is showing
const shouldHideButtons = isMobile && openMobile;

Expand Down Expand Up @@ -69,11 +65,6 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
}
};

const handleSelectSession = async (sessionId: string) => {
// Navigate to chat with session data
navigate('/', { state: { sessionId } });
};

const handleNewWindow = () => {
window.electron.createChatWindow(
undefined,
Expand All @@ -83,17 +74,22 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }

return (
<div className="flex flex-1 w-full relative animate-fade-in">
{/* Global background with aggressive blur for chat sections */}
<GlobalBackground blur={true} opacity={0.3} />
{/* Global background */}
<GlobalBackground blur={false} opacity={1} />

{/* Floating pill navigation in center */}
<div className="absolute top-4 left-0 right-0 z-50 flex justify-center pointer-events-none">
<div className="pointer-events-auto">
<PillSideNav />
</div>
</div>

{/* New Window button in top right */}
{!shouldHideButtons && (
<div className={`${headerPadding} absolute top-3 z-100 flex items-center`}>
<SidebarTrigger
className={`no-drag hover:border-border-strong hover:text-text-default hover:!bg-background-medium hover:scale-105`}
/>
<div className="absolute top-4 right-4 z-50">
<Button
onClick={handleNewWindow}
className="no-drag hover:!bg-background-medium"
className="no-drag hover:!bg-white/10 text-white"
variant="ghost"
size="xs"
title="Start a new session in a new window"
Expand All @@ -102,17 +98,11 @@ const AppLayoutContent: React.FC<AppLayoutProps> = ({ setIsGoosehintsModalOpen }
</Button>
</div>
)}
<Sidebar variant="inset" collapsible="offcanvas">
<AppSidebar
onSelectSession={handleSelectSession}
setView={setView}
setIsGoosehintsModalOpen={setIsGoosehintsModalOpen}
currentPath={location.pathname}
/>
</Sidebar>
<SidebarInset>

{/* Main Content */}
<div className="w-full h-full">
<Outlet />
</SidebarInset>
</div>
</div>
);
};
Expand Down
126 changes: 126 additions & 0 deletions ui/desktop/src/components/PillSideNav.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import React, { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { cn } from '../utils';
import {
Home as HomeIcon,
MessageSquare as ChatIcon,
Clock as ClockIcon,
FileText as FileIcon,
Puzzle as PuzzleIcon,
Settings as SettingsIcon,
History as HistoryIcon,
ChevronRight as ChevronRightIcon,
X as CloseIcon
} from 'lucide-react';

interface NavItemProps {
icon: React.ReactNode;
label: string;
path: string;
isActive?: boolean;
onClick: () => void;
}

const NavItem: React.FC<NavItemProps> = ({
icon,
label,
isActive = false,
onClick
}) => {
return (
<button
onClick={onClick}
className={cn(
"flex items-center gap-2 px-3 py-2 rounded-full transition-all duration-200 w-full",
isActive
? "bg-white/15 text-white"
: "text-white/80 hover:text-white hover:bg-white/10"
)}
>
<div className="text-lg">
{icon}
</div>
<span className="text-sm font-medium">{label}</span>
</button>
);
};

export const PillSideNav: React.FC = () => {
const navigate = useNavigate();
const location = useLocation();
const currentPath = location.pathname;
const [isExpanded, setIsExpanded] = useState(false);

const handleNavigation = (path: string) => {
navigate(path);
setIsExpanded(false);
};

// Navigation items configuration
const navItems = [
{ icon: <HomeIcon size={18} />, label: 'Home', path: '/' },
{ icon: <ChatIcon size={18} />, label: 'Chat', path: '/pair' },
{ icon: <HistoryIcon size={18} />, label: 'History', path: '/sessions' },
{ icon: <FileIcon size={18} />, label: 'Recipes', path: '/recipes' },
{ icon: <SettingsIcon size={18} />, label: 'Settings', path: '/settings' },
];

// Find the current active item
const activeItem = navItems.find(item => item.path === currentPath) || navItems[0];

// Get current mode label
const currentModeLabel = activeItem.label;

return (
<div className="relative">
{/* Collapsed Pill */}
{!isExpanded && (
<div
className="h-9 bg-white/10 backdrop-blur-md rounded-full
flex items-center cursor-pointer shadow-md
transition-all duration-300 hover:bg-white/15 px-4
border border-white/20"
onClick={() => setIsExpanded(true)}
>
<span className="text-white font-medium text-sm">{currentModeLabel}</span>
</div>
)}

{/* Expanded Navigation */}
{isExpanded && (
<div className="absolute left-1/2 transform -translate-x-1/2 top-0 z-50
bg-black/70 backdrop-blur-xl
border border-white/20 shadow-xl rounded-lg animate-in fade-in duration-200">
<div className="flex flex-col p-2">
{/* Header with close button */}
<div className="flex items-center justify-between px-2 py-1 mb-1">
<span className="text-white font-medium text-sm">Navigation</span>
<button
onClick={() => setIsExpanded(false)}
className="text-white/70 hover:text-white p-1 rounded-full hover:bg-white/10"
>
<CloseIcon size={16} />
</button>
</div>

{/* Navigation Items */}
<div className="space-y-1 min-w-40">
{navItems.map((item) => (
<NavItem
key={item.path}
icon={item.icon}
label={item.label}
path={item.path}
isActive={currentPath === item.path}
onClick={() => handleNavigation(item.path)}
/>
))}
</div>
</div>
</div>
)}
</div>
);
};

export default PillSideNav;
120 changes: 108 additions & 12 deletions ui/desktop/src/components/pair.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,66 @@ export default function Pair({
// Get recipe configuration and parameter handling
const { initialPrompt: recipeInitialPrompt } = useRecipeManager(chat.messages, location.state);

// Get sidebar state for background adjustments
const { state: currentSidebarState } = useSidebar();
const isSidebarCollapsed = currentSidebarState === 'collapsed';

// Override backgrounds to allow our gradient to show through
useEffect(() => {
// Override SidebarInset background
// Target the specific SidebarInset component with the complex class
const sidebarInset = document.querySelector('[data-slot="sidebar-inset"]') as HTMLElement;
if (sidebarInset) {
sidebarInset.style.background = 'transparent';
sidebarInset.style.backgroundColor = 'transparent';
// Remove the bg-background class that might be causing the issue
sidebarInset.classList.remove('bg-background');
// Add bg-transparent class
sidebarInset.classList.add('bg-transparent');
}

// Target the sidebar element itself
const sidebar = document.querySelector('[data-slot="sidebar"]') as HTMLElement;
if (sidebar) {
// Add a style to make sure it doesn't block our background
sidebar.style.pointerEvents = 'auto';
sidebar.style.zIndex = '20';
// Make the sidebar background transparent
sidebar.style.background = 'transparent';
sidebar.style.backgroundColor = 'transparent';
}

// Target the sidebar wrapper
const sidebarWrapper = document.querySelector('[data-slot="sidebar-wrapper"]') as HTMLElement;
if (sidebarWrapper) {
sidebarWrapper.style.background = 'transparent';
sidebarWrapper.style.backgroundColor = 'transparent';
}

// Target the sidebar container
const sidebarContainer = document.querySelector('[data-slot="sidebar-container"]') as HTMLElement;
if (sidebarContainer) {
sidebarContainer.style.background = 'transparent';
sidebarContainer.style.backgroundColor = 'transparent';
}

// Target the sidebar inner
const sidebarInner = document.querySelector('[data-slot="sidebar-inner"]') as HTMLElement;
if (sidebarInner) {
// Keep the sidebar's own background but make sure it doesn't extend
sidebarInner.style.width = '100%';
sidebarInner.style.height = '100%';
}

// Apply reduced blur effect to ChatInput (same as Hub but less aggressive)
// Override MainPanelLayout background
const mainPanels = document.querySelectorAll('.bg-background-default, .bg-background-muted') as NodeListOf<HTMLElement>;
mainPanels.forEach(panel => {
if (panel) {
panel.style.background = 'transparent';
panel.style.backgroundColor = 'transparent';
}
});

// Override ChatInput background to be transparent with glass effect
const chatInputContainer = document.querySelector('[data-drop-zone="true"]') as HTMLElement;
if (chatInputContainer) {
chatInputContainer.style.background = 'rgba(255, 255, 255, 0.05)';
Expand All @@ -82,7 +133,37 @@ export default function Pair({
return () => {
if (sidebarInset) {
sidebarInset.style.background = '';
sidebarInset.style.backgroundColor = '';
sidebarInset.classList.remove('bg-transparent');
// Restore the original class if needed
if (!sidebarInset.classList.contains('bg-background')) {
sidebarInset.classList.add('bg-background');
}
}
if (sidebar) {
sidebar.style.pointerEvents = '';
sidebar.style.zIndex = '';
sidebar.style.background = '';
sidebar.style.backgroundColor = '';
}
if (sidebarWrapper) {
sidebarWrapper.style.background = '';
sidebarWrapper.style.backgroundColor = '';
}
if (sidebarContainer) {
sidebarContainer.style.background = '';
sidebarContainer.style.backgroundColor = '';
}
if (sidebarInner) {
sidebarInner.style.width = '';
sidebarInner.style.height = '';
}
mainPanels.forEach(panel => {
if (panel) {
panel.style.background = '';
panel.style.backgroundColor = '';
}
});
if (chatInputContainer) {
chatInputContainer.style.background = '';
chatInputContainer.style.backdropFilter = '';
Expand All @@ -91,7 +172,7 @@ export default function Pair({
chatInputContainer.style.border = '';
}
};
});
}, []);

// Handle recipe loading from recipes view - reset chat if needed
useEffect(() => {
Expand Down Expand Up @@ -222,8 +303,11 @@ export default function Pair({

// Custom main layout props to override background completely
const customMainLayoutProps = {
backgroundColor: '', // Remove any background class
style: { backgroundColor: 'transparent' }, // Force transparent background with inline style
backgroundColor: 'transparent', // Use transparent instead of empty string
style: {
backgroundColor: 'transparent',
background: 'transparent'
}, // Force transparent background with inline style
};

// Custom content before messages
Expand All @@ -232,13 +316,25 @@ export default function Pair({
};

return (
<div className="flex flex-col h-full relative" style={{ backgroundColor: 'transparent' }}>
{/* Use GlobalBackground to respect user's selected background */}
<GlobalBackground blur={false} opacity={1} />

<div className="flex flex-col h-full relative bg-transparent">
{/* Image background implementation */}
<div className="fixed inset-0 -z-10"
style={{
backgroundImage: `url('/background.jpg')`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
}}
/>

{/* Optional overlay for better text readability */}
<div
className="fixed inset-0 -z-10 bg-black/20"
/>

{/* Centered chat content */}
<div className="relative z-10 flex justify-center h-full" style={{ backgroundColor: 'transparent' }}>
<div className="w-full max-w-[1000px] h-full" style={{ backgroundColor: 'transparent' }}>
<div className="relative z-10 flex justify-center h-full bg-transparent">
<div className="w-full max-w-[1000px] h-full bg-transparent">
<BaseChat
chat={chat}
setChat={setChat}
Expand All @@ -250,7 +346,7 @@ export default function Pair({
renderBeforeMessages={renderBeforeMessages}
customChatInputProps={customChatInputProps}
customMainLayoutProps={customMainLayoutProps} // Override background
contentClassName={cn('pr-1 pb-10', (isMobile || sidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state
contentClassName={cn('pr-1 pb-10', (isMobile || currentSidebarState === 'collapsed') && 'pt-11')} // Use dynamic content class with mobile margin and sidebar state
showPopularTopics={!isTransitioningFromHub} // Don't show popular topics while transitioning from Hub
suppressEmptyState={isTransitioningFromHub} // Suppress all empty state content while transitioning from Hub
/>
Expand Down