diff --git a/src/renderer/src/components/Popups/FloatingSidebar.tsx b/src/renderer/src/components/Popups/FloatingSidebar.tsx new file mode 100644 index 0000000000..df77619d90 --- /dev/null +++ b/src/renderer/src/components/Popups/FloatingSidebar.tsx @@ -0,0 +1,90 @@ +import HomeTabs from '@renderer/pages/home/Tabs/index' +import { Assistant, Topic } from '@renderer/types' +import { Popover } from 'antd' +import { FC, useEffect, useState } from 'react' +import { useHotkeys } from 'react-hotkeys-hook' +import styled from 'styled-components' + +import Scrollbar from '../Scrollbar' + +interface Props { + children: React.ReactNode + activeAssistant: Assistant + setActiveAssistant: (assistant: Assistant) => void + activeTopic: Topic + setActiveTopic: (topic: Topic) => void + position: 'left' | 'right' +} + +const FloatingSidebar: FC = ({ + children, + activeAssistant, + setActiveAssistant, + activeTopic, + setActiveTopic, + position = 'left' +}) => { + const [open, setOpen] = useState(false) + + useHotkeys('esc', () => { + setOpen(false) + }) + + const [maxHeight, setMaxHeight] = useState(Math.floor(window.innerHeight * 0.75)) + + useEffect(() => { + const handleResize = () => { + setMaxHeight(Math.floor(window.innerHeight * 0.75)) + } + + window.addEventListener('resize', handleResize) + + return () => { + window.removeEventListener('resize', handleResize) + } + }, []) + + const content = ( + + + + ) + + return ( + { + setOpen(visible) + }} + content={content} + trigger={['hover', 'click']} + placement="bottomRight" + arrow={false} + mouseEnterDelay={0.8} // 800ms delay before showing + mouseLeaveDelay={20} + styles={{ + body: { + padding: 0, + background: 'var(--color-background)', + border: '1px solid var(--color-border)', + borderRadius: '8px', + boxShadow: '0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 3px 6px -4px rgba(0, 0, 0, 0.12)' + } + }}> + {children} + + ) +} + +const PopoverContent = styled(Scrollbar)<{ maxHeight: number }>` + max-height: ${(props) => props.maxHeight}px; + overflow-y: auto; +` + +export default FloatingSidebar diff --git a/src/renderer/src/pages/home/HomePage.tsx b/src/renderer/src/pages/home/HomePage.tsx index d233cdce4a..3bc47468fd 100644 --- a/src/renderer/src/pages/home/HomePage.tsx +++ b/src/renderer/src/pages/home/HomePage.tsx @@ -1,6 +1,7 @@ import { useAssistants } from '@renderer/hooks/useAssistant' import { useSettings } from '@renderer/hooks/useSettings' import { useActiveTopic } from '@renderer/hooks/useTopic' +import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import NavigationService from '@renderer/services/NavigationService' import { Assistant } from '@renderer/types' import { FC, useEffect, useState } from 'react' @@ -36,6 +37,19 @@ const HomePage: FC = () => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [state]) + useEffect(() => { + const unsubscribe = EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => { + const newAssistant = assistants.find((a) => a.id === assistantId) + if (newAssistant) { + setActiveAssistant(newAssistant) + } + }) + + return () => { + unsubscribe() + } + }, [assistants, setActiveAssistant]) + useEffect(() => { const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics window.api.window.setMinimumSize(canMinimize ? 520 : 1080, 600) @@ -47,7 +61,13 @@ const HomePage: FC = () => { return ( - + {showAssistants && ( void + setActiveAssistant: (assistant: Assistant) => void + position: 'left' | 'right' } -const HeaderNavbar: FC = ({ activeAssistant }) => { +const HeaderNavbar: FC = ({ activeAssistant, setActiveAssistant, activeTopic, setActiveTopic }) => { const { assistant } = useAssistant(activeAssistant.id) const { showAssistants, toggleShowAssistants } = useShowAssistants() const { topicPosition, sidebarIcons, narrowMode } = useSettings() const { showTopics, toggleShowTopics } = useShowTopics() const dispatch = useAppDispatch() + const [sidebarHideCooldown, setSidebarHideCooldown] = useState(false) - useShortcut('toggle_show_assistants', () => { - toggleShowAssistants() - }) + // Function to toggle assistants with cooldown + const handleToggleShowAssistants = useCallback(() => { + if (showAssistants) { + // When hiding sidebar, set cooldown + toggleShowAssistants() + setSidebarHideCooldown(true) + // setTimeout(() => { + // setSidebarHideCooldown(false) + // }, 10000) // 10 seconds cooldown + } else { + // When showing sidebar, no cooldown needed + toggleShowAssistants() + } + }, [showAssistants, toggleShowAssistants]) + const handleToggleShowTopics = useCallback(() => { + if (showTopics) { + // When hiding sidebar, set cooldown + toggleShowTopics() + setSidebarHideCooldown(true) + // setTimeout(() => { + // setSidebarHideCooldown(false) + // }, 10000) // 10 seconds cooldown + } else { + // When showing sidebar, no cooldown needed + toggleShowTopics() + } + }, [showTopics, toggleShowTopics]) + + useShortcut('toggle_show_assistants', handleToggleShowAssistants) useShortcut('toggle_show_topics', () => { if (topicPosition === 'right') { @@ -60,7 +90,7 @@ const HeaderNavbar: FC = ({ activeAssistant }) => { {showAssistants && ( - + @@ -73,11 +103,28 @@ const HeaderNavbar: FC = ({ activeAssistant }) => { )} - {!showAssistants && ( + {!showAssistants && !sidebarHideCooldown && ( + + + toggleShowAssistants()} + style={{ marginRight: 8, marginLeft: isMac ? 4 : -12 }}> + + + + + )} + {!showAssistants && sidebarHideCooldown && ( toggleShowAssistants()} - style={{ marginRight: 8, marginLeft: isMac ? 4 : -12 }}> + style={{ marginRight: 8, marginLeft: isMac ? 4 : -12 }} + onMouseOut={() => setSidebarHideCooldown(false)}> @@ -105,10 +152,33 @@ const HeaderNavbar: FC = ({ activeAssistant }) => { )} - {topicPosition === 'right' && ( - - {showTopics ? : } - + {topicPosition === 'right' && !showTopics && !sidebarHideCooldown && ( + + + toggleShowTopics()}> + + + + + )} + {topicPosition === 'right' && !showTopics && sidebarHideCooldown && ( + + toggleShowTopics()} onMouseOut={() => setSidebarHideCooldown(false)}> + + + + )} + {topicPosition === 'right' && showTopics && ( + + handleToggleShowTopics()}> + + + )} diff --git a/src/renderer/src/pages/home/Tabs/index.tsx b/src/renderer/src/pages/home/Tabs/index.tsx index 59aca8bf72..1806e3a237 100644 --- a/src/renderer/src/pages/home/Tabs/index.tsx +++ b/src/renderer/src/pages/home/Tabs/index.tsx @@ -20,18 +20,26 @@ interface Props { setActiveAssistant: (assistant: Assistant) => void setActiveTopic: (topic: Topic) => void position: 'left' | 'right' + forceToSeeAllTab?: boolean } type Tab = 'assistants' | 'topic' | 'settings' let _tab: any = '' -const HomeTabs: FC = ({ activeAssistant, activeTopic, setActiveAssistant, setActiveTopic, position }) => { +const HomeTabs: FC = ({ + activeAssistant, + activeTopic, + setActiveAssistant, + setActiveTopic, + position, + forceToSeeAllTab +}) => { const { addAssistant } = useAssistants() const [tab, setTab] = useState(position === 'left' ? _tab || 'assistants' : 'topic') const { topicPosition } = useSettings() const { defaultAssistant } = useDefaultAssistant() - const { toggleShowTopics } = useShowTopics() + const { showTopics, toggleShowTopics } = useShowTopics() const { t } = useTranslation() @@ -86,20 +94,22 @@ const HomeTabs: FC = ({ activeAssistant, activeTopic, setActiveAssistant, if (position === 'right' && topicPosition === 'right' && tab === 'assistants') { setTab('topic') } - if (position === 'left' && topicPosition === 'right' && tab !== 'assistants') { + if (position === 'left' && topicPosition === 'right' && forceToSeeAllTab != true && tab !== 'assistants') { setTab('assistants') } - }, [position, tab, topicPosition]) + }, [position, tab, topicPosition, forceToSeeAllTab]) return ( - {showTab && ( + {(showTab || (forceToSeeAllTab == true && !showTopics)) && (