Files
tkmind_go/ui/desktop/src/components/GooseSidebar/AppSidebar.tsx
T

173 lines
4.3 KiB
TypeScript

import React, { useEffect } from 'react';
import { FileText, Clock, Home, Puzzle, History } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import {
SidebarContent,
SidebarFooter,
SidebarMenu,
SidebarMenuItem,
SidebarMenuButton,
SidebarGroup,
SidebarGroupContent,
SidebarSeparator,
} from '../ui/sidebar';
import { ChatSmart, Gear } from '../icons';
import { ViewOptions, View } from '../../App';
import { useChatContext } from '../../contexts/ChatContext';
import { DEFAULT_CHAT_TITLE } from '../../contexts/ChatContext';
interface SidebarProps {
onSelectSession: (sessionId: string) => void;
refreshTrigger?: number;
children?: React.ReactNode;
setIsGoosehintsModalOpen?: (isOpen: boolean) => void;
setView?: (view: View, viewOptions?: ViewOptions) => void;
currentPath?: string;
}
interface NavigationItem {
type: 'item';
path: string;
label: string;
icon: React.ComponentType<{ className?: string }>;
tooltip: string;
}
interface NavigationSeparator {
type: 'separator';
}
type NavigationEntry = NavigationItem | NavigationSeparator;
const menuItems: NavigationEntry[] = [
{
type: 'item',
path: '/',
label: 'Home',
icon: Home,
tooltip: 'Go back to the main chat screen',
},
{ type: 'separator' },
{
type: 'item',
path: '/pair',
label: 'Chat',
icon: ChatSmart,
tooltip: 'Start pairing with Goose',
},
{
type: 'item',
path: '/sessions',
label: 'History',
icon: History,
tooltip: 'View your session history',
},
{ type: 'separator' },
{
type: 'item',
path: '/recipes',
label: 'Recipes',
icon: FileText,
tooltip: 'Browse your saved recipes',
},
{
type: 'item',
path: '/schedules',
label: 'Scheduler',
icon: Clock,
tooltip: 'Manage scheduled runs',
},
{
type: 'item',
path: '/extensions',
label: 'Extensions',
icon: Puzzle,
tooltip: 'Manage your extensions',
},
{ type: 'separator' },
{
type: 'item',
path: '/settings',
label: 'Settings',
icon: Gear,
tooltip: 'Configure Goose settings',
},
];
const AppSidebar: React.FC<SidebarProps> = ({ currentPath }) => {
const navigate = useNavigate();
const chatContext = useChatContext();
useEffect(() => {
const timer = setTimeout(() => {
// setIsVisible(true);
}, 100);
// eslint-disable-next-line no-undef
return () => clearTimeout(timer);
}, []);
useEffect(() => {
const currentItem = menuItems.find(
(item) => item.type === 'item' && item.path === currentPath
) as NavigationItem | undefined;
const titleBits = ['Goose'];
if (
currentPath === '/pair' &&
chatContext?.chat?.title &&
chatContext.chat.title !== DEFAULT_CHAT_TITLE
) {
titleBits.push(chatContext.chat.title);
} else if (currentPath !== '/' && currentItem) {
titleBits.push(currentItem.label);
}
document.title = titleBits.join(' - ');
}, [currentPath, chatContext?.chat?.title]);
const isActivePath = (path: string) => {
return currentPath === path;
};
const renderMenuItem = (entry: NavigationEntry, index: number) => {
if (entry.type === 'separator') {
return <SidebarSeparator key={index} />;
}
const IconComponent = entry.icon;
return (
<SidebarGroup key={entry.path}>
<SidebarGroupContent className="space-y-1">
<div className="sidebar-item">
<SidebarMenuItem>
<SidebarMenuButton
onClick={() => navigate(entry.path)}
isActive={isActivePath(entry.path)}
tooltip={entry.tooltip}
className="w-full justify-start px-3 rounded-lg h-fit hover:bg-background-medium/50 transition-all duration-200 data-[active=true]:bg-background-medium"
>
<IconComponent className="w-4 h-4" />
<span>{entry.label}</span>
</SidebarMenuButton>
</SidebarMenuItem>
</div>
</SidebarGroupContent>
</SidebarGroup>
);
};
return (
<>
<SidebarContent className="pt-16">
<SidebarMenu>{menuItems.map((entry, index) => renderMenuItem(entry, index))}</SidebarMenu>
</SidebarContent>
<SidebarFooter />
</>
);
};
export default AppSidebar;