Merge remote-tracking branch 'origin/feat/sidebar' into feat/cherry-store-render

This commit is contained in:
MyPrototypeWhat 2025-06-09 17:49:42 +08:00
commit f2c52dfe89
17 changed files with 181 additions and 261 deletions

View File

@ -11,13 +11,13 @@ if (isDev) {
export const DATA_PATH = getDataPath() export const DATA_PATH = getDataPath()
export const titleBarOverlayDark = { export const titleBarOverlayDark = {
height: 40, height: 42,
color: 'rgba(255,255,255,0)', color: 'rgba(255,255,255,0)',
symbolColor: '#fff' symbolColor: '#fff'
} }
export const titleBarOverlayLight = { export const titleBarOverlayLight = {
height: 40, height: 42,
color: 'rgba(255,255,255,0)', color: 'rgba(255,255,255,0)',
symbolColor: '#000' symbolColor: '#000'
} }

View File

@ -56,14 +56,14 @@ export class WindowService {
minHeight: 600, minHeight: 600,
show: false, show: false,
autoHideMenuBar: true, autoHideMenuBar: true,
transparent: isMac, transparent: false,
vibrancy: 'sidebar', vibrancy: 'sidebar',
visualEffectState: 'active', visualEffectState: 'active',
titleBarStyle: 'hidden', titleBarStyle: 'hidden',
titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight, titleBarOverlay: nativeTheme.shouldUseDarkColors ? titleBarOverlayDark : titleBarOverlayLight,
backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF', backgroundColor: isMac ? undefined : nativeTheme.shouldUseDarkColors ? '#181818' : '#FFFFFF',
darkTheme: nativeTheme.shouldUseDarkColors, darkTheme: nativeTheme.shouldUseDarkColors,
trafficLightPosition: { x: 15, y: 12 }, trafficLightPosition: { x: 12, y: 12 },
...(isLinux ? { icon } : {}), ...(isLinux ? { icon } : {}),
webPreferences: { webPreferences: {
preload: join(__dirname, '../preload/index.js'), preload: join(__dirname, '../preload/index.js'),

View File

@ -29,7 +29,7 @@
--color-text-secondary: rgba(235, 235, 245, 0.7); --color-text-secondary: rgba(235, 235, 245, 0.7);
--color-icon: #ffffff99; --color-icon: #ffffff99;
--color-icon-white: #ffffff; --color-icon-white: #ffffff;
--color-border: #000; --color-border: #383838;
--color-border-soft: #ffffff10; --color-border-soft: #ffffff10;
--color-border-mute: #ffffff05; --color-border-mute: #ffffff05;
--color-error: #f44336; --color-error: #f44336;
@ -56,7 +56,7 @@
--navbar-background-mac: rgba(20, 20, 20, 0.55); --navbar-background-mac: rgba(20, 20, 20, 0.55);
--navbar-background: #1f1f1f; --navbar-background: #1f1f1f;
--navbar-height: 45px; --navbar-height: 42px;
--sidebar-width: 50px; --sidebar-width: 50px;
--status-bar-height: 40px; --status-bar-height: 40px;
--input-bar-height: 100px; --input-bar-height: 100px;

View File

@ -1,16 +1,27 @@
import { isMac } from '@renderer/config/constant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { MessageSquareDiff } from 'lucide-react' import { MessageSquareDiff, Search } from 'lucide-react'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import SearchPopup from '../Popups/SearchPopup'
interface Props {} interface Props {}
const HeaderNavbar: FC<Props> = () => { const HeaderNavbar: FC<Props> = () => {
return ( return (
<Container> <Container>
<div></div> <div>
{!isMac && (
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={() => SearchPopup.show()}>
<Search size={18} />
</NarrowIcon>
</Tooltip>
)}
</div>
<Tooltip title={t('settings.shortcuts.new_topic')} mouseEnterDelay={0.8}> <Tooltip title={t('settings.shortcuts.new_topic')} mouseEnterDelay={0.8}>
<NavbarIcon onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} style={{ marginRight: 5 }}> <NavbarIcon onClick={() => EventEmitter.emit(EVENT_NAMES.ADD_NEW_TOPIC)} style={{ marginRight: 5 }}>
<MessageSquareDiff size={18} /> <MessageSquareDiff size={18} />
@ -32,6 +43,7 @@ const Container = styled.div`
min-height: var(--navbar-height); min-height: var(--navbar-height);
background-color: transparent; background-color: transparent;
-webkit-app-region: drag; -webkit-app-region: drag;
padding-left: ${isMac ? '75px' : '0'};
` `
export const NavbarIcon = styled.div` export const NavbarIcon = styled.div`
@ -69,4 +81,10 @@ export const NavbarIcon = styled.div`
} }
` `
const NarrowIcon = styled(NavbarIcon)`
@media (max-width: 1000px) {
display: none;
}
`
export default HeaderNavbar export default HeaderNavbar

View File

@ -15,14 +15,13 @@ import {
Bot, Bot,
ChevronDown, ChevronDown,
ChevronRight, ChevronRight,
Compass,
FileSearch, FileSearch,
Folder, Folder,
Languages, Languages,
LayoutGrid,
MessageSquare, MessageSquare,
Moon, Moon,
Palette, Palette,
Sparkle,
SquareTerminal, SquareTerminal,
Sun, Sun,
SunMoon SunMoon
@ -48,7 +47,6 @@ const MainSidebar: FC = () => {
const [isAppMenuExpanded, setIsAppMenuExpanded] = useState(false) const [isAppMenuExpanded, setIsAppMenuExpanded] = useState(false)
const location = useLocation() const location = useLocation()
const state = location.state
const { pathname } = location const { pathname } = location
const { activeAssistant, activeTopic, setActiveAssistant, setActiveTopic } = useChat() const { activeAssistant, activeTopic, setActiveAssistant, setActiveTopic } = useChat()
@ -59,10 +57,9 @@ const MainSidebar: FC = () => {
}, [navigate]) }, [navigate])
useEffect(() => { useEffect(() => {
state?.assistant && setActiveAssistant(state?.assistant) const unsubscribe = EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, () => setTab('topic'))
state?.topic && setActiveTopic(state?.topic) return () => unsubscribe()
// eslint-disable-next-line react-hooks/exhaustive-deps }, [])
}, [state])
useEffect(() => { useEffect(() => {
const unsubscribe = EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => { const unsubscribe = EventEmitter.on(EVENT_NAMES.SWITCH_ASSISTANT, (assistantId: string) => {
@ -78,7 +75,7 @@ const MainSidebar: FC = () => {
}, [assistants, setActiveAssistant]) }, [assistants, setActiveAssistant])
useEffect(() => { useEffect(() => {
const canMinimize = topicPosition == 'left' ? !showAssistants : !showAssistants && !showTopics const canMinimize = !showAssistants && !showTopics
window.api.window.setMinimumSize(canMinimize ? 520 : 1080, 600) window.api.window.setMinimumSize(canMinimize ? 520 : 1080, 600)
return () => { return () => {
@ -86,6 +83,10 @@ const MainSidebar: FC = () => {
} }
}, [showAssistants, showTopics, topicPosition]) }, [showAssistants, showTopics, topicPosition])
useEffect(() => {
setIsAppMenuExpanded(false)
}, [activeAssistant.id, activeTopic.id])
const onAvatarClick = () => { const onAvatarClick = () => {
navigate('/settings/provider') navigate('/settings/provider')
} }
@ -96,14 +97,15 @@ const MainSidebar: FC = () => {
} }
const appMenuItems = [ const appMenuItems = [
{ icon: <Sparkle size={18} className="icon" />, text: t('agents.title'), path: '/agents' }, // { icon: <Sparkle size={18} className="icon" />, text: t('agents.title'), path: '/agents' },
{ icon: <Compass size={18} className="icon" />, text: t('discover.title'), path: '/discover' },
{ icon: <Languages size={18} className="icon" />, text: t('translate.title'), path: '/translate' }, { icon: <Languages size={18} className="icon" />, text: t('translate.title'), path: '/translate' },
{ {
icon: <Palette size={18} className="icon" />, icon: <Palette size={18} className="icon" />,
text: t('paintings.title'), text: t('paintings.title'),
path: `/paintings/${defaultPaintingProvider}` path: `/paintings/${defaultPaintingProvider}`
}, },
{ icon: <LayoutGrid size={18} className="icon" />, text: t('minapp.title'), path: '/apps' }, // { icon: <LayoutGrid size={18} className="icon" />, text: t('minapp.title'), path: '/apps' },
{ icon: <FileSearch size={18} className="icon" />, text: t('knowledge.title'), path: '/knowledge' }, { icon: <FileSearch size={18} className="icon" />, text: t('knowledge.title'), path: '/knowledge' },
{ icon: <SquareTerminal size={18} className="icon" />, text: t('common.mcp'), path: '/mcp-servers' }, { icon: <SquareTerminal size={18} className="icon" />, text: t('common.mcp'), path: '/mcp-servers' },
{ icon: <Folder size={18} className="icon" />, text: t('files.title'), path: '/files' } { icon: <Folder size={18} className="icon" />, text: t('files.title'), path: '/files' }
@ -111,10 +113,6 @@ const MainSidebar: FC = () => {
const isRoutes = (path: string): boolean => pathname.startsWith(path) const isRoutes = (path: string): boolean => pathname.startsWith(path)
if (location.pathname !== '/') {
return null
}
const onChageTab = (tab: Tab) => { const onChageTab = (tab: Tab) => {
setTab(tab) setTab(tab)
setIsAppMenuExpanded(false) setIsAppMenuExpanded(false)
@ -124,6 +122,10 @@ const MainSidebar: FC = () => {
return null return null
} }
if (location.pathname !== '/') {
return null
}
return ( return (
<Container id="main-sidebar"> <Container id="main-sidebar">
<MainNavbar /> <MainNavbar />
@ -187,7 +189,6 @@ const MainSidebar: FC = () => {
activeTopic={activeTopic} activeTopic={activeTopic}
setActiveAssistant={setActiveAssistant} setActiveAssistant={setActiveAssistant}
setActiveTopic={setActiveTopic} setActiveTopic={setActiveTopic}
position="left"
/> />
<UserMenu onClick={onAvatarClick}> <UserMenu onClick={onAvatarClick}>
<UserMenuLeft> <UserMenuLeft>

View File

@ -1,7 +1,7 @@
import { isLinux, isMac, isWindows } from '@renderer/config/constant' import { isLinux, isMac, isWindows } from '@renderer/config/constant'
import { useFullscreen } from '@renderer/hooks/useFullscreen' import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { Button } from 'antd' import { Button } from 'antd'
import { X } from 'lucide-react' import { ChevronDown, X } from 'lucide-react'
import type { FC, PropsWithChildren } from 'react' import type { FC, PropsWithChildren } from 'react'
import type { HTMLAttributes } from 'react' import type { HTMLAttributes } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
@ -28,16 +28,46 @@ export const NavbarRight: FC<Props> = ({ children, ...props }) => {
export const NavbarMain: FC<Props> = ({ children, ...props }) => { export const NavbarMain: FC<Props> = ({ children, ...props }) => {
const isFullscreen = useFullscreen() const isFullscreen = useFullscreen()
const navigate = useNavigate()
return ( return (
<NavbarMainContainer {...props} $isFullscreen={isFullscreen}> <NavbarMainContainer {...props} $isFullscreen={isFullscreen}>
<CloseIconWindows />
{children} {children}
<Button type="text" icon={<X size={18} />} onClick={() => navigate('/')} className="nodrag"></Button> <CloseIconMac />
</NavbarMainContainer> </NavbarMainContainer>
) )
} }
const CloseIconMac = () => {
const navigate = useNavigate()
if (!isMac) {
return null
}
return <Button type="text" icon={<X size={18} />} onClick={() => navigate('/')} className="nodrag" />
}
const CloseIconWindows = () => {
const navigate = useNavigate()
if (isMac) {
return null
}
return (
<Button
size="small"
type="default"
shape="circle"
icon={<ChevronDown size={16} />}
onClick={() => navigate('/')}
className="nodrag"
style={{ marginRight: 5 }}
/>
)
}
const NavbarContainer = styled.div` const NavbarContainer = styled.div`
min-width: 100%; min-width: 100%;
display: flex; display: flex;

View File

@ -47,7 +47,7 @@ As [role name], with [list skills], strictly adhering to [list constraints], usi
` `
export const SUMMARIZE_PROMPT = export const SUMMARIZE_PROMPT =
"You are an assistant skilled in conversation. You need to summarize the user's conversation into a title within 10 words. The language of the title should be consistent with the user's primary language. Do not use punctuation marks or other special symbols" "You are an assistant skilled in conversation. You need to summarize the user's conversation into a title within 10 words. The language of the title should be consistent with the user's primary language. Do not use punctuation marks, markdown language markers, or other special symbols"
// https://github.com/ItzCrazyKns/Perplexica/blob/master/src/lib/prompts/webSearch.ts // https://github.com/ItzCrazyKns/Perplexica/blob/master/src/lib/prompts/webSearch.ts
export const SEARCH_SUMMARY_PROMPT = ` export const SEARCH_SUMMARY_PROMPT = `

View File

@ -4,15 +4,16 @@ import { setActiveAssistant, setActiveTopic } from '@renderer/store/runtime'
import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk' import { loadTopicMessagesThunk } from '@renderer/store/thunk/messageThunk'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { Topic } from '@renderer/types' import { Topic } from '@renderer/types'
import { find } from 'lodash'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useAssistants } from './useAssistant' import { useAssistants } from './useAssistant'
import { useSettings } from './useSettings'
export const useChat = () => { export const useChat = () => {
const { assistants } = useAssistants() const { assistants } = useAssistants()
const activeAssistant = useAppSelector((state) => state.runtime.chat.activeAssistant) || assistants[0] const activeAssistant = useAppSelector((state) => state.runtime.chat.activeAssistant) || assistants[0]
const activeTopic = useAppSelector((state) => state.runtime.chat.activeTopic) || activeAssistant?.topics[0]! const activeTopic = useAppSelector((state) => state.runtime.chat.activeTopic) || activeAssistant?.topics[0]!
const { clickAssistantToShowTopic } = useSettings()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
useEffect(() => { useEffect(() => {
@ -23,11 +24,15 @@ export const useChat = () => {
}, [activeTopic, dispatch]) }, [activeTopic, dispatch])
useEffect(() => { useEffect(() => {
// activeTopic not in assistant.topics const firstTopic = activeAssistant.topics[0]
if (activeAssistant && !find(activeAssistant.topics, { id: activeTopic?.id })) { firstTopic && dispatch(setActiveTopic(firstTopic))
dispatch(setActiveTopic(activeAssistant.topics[0])) }, [activeAssistant, dispatch])
useEffect(() => {
if (clickAssistantToShowTopic) {
EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)
} }
}, [activeTopic?.id, activeAssistant, dispatch]) }, [clickAssistantToShowTopic, activeAssistant])
return { return {
activeAssistant, activeAssistant,

View File

@ -1,4 +1,4 @@
import { Navbar, NavbarCenter } from '@renderer/components/app/Navbar' import { NavbarCenter, NavbarMain } from '@renderer/components/app/Navbar'
// import { useRuntime } from '@renderer/hooks/useRuntime' // No longer needed if resourcesPath is not used // import { useRuntime } from '@renderer/hooks/useRuntime' // No longer needed if resourcesPath is not used
import { Tabs as VercelTabs } from '@renderer/ui/vercel-tabs' import { Tabs as VercelTabs } from '@renderer/ui/vercel-tabs'
import { useEffect } from 'react' import { useEffect } from 'react'
@ -45,9 +45,9 @@ export default function DiscoverPage() {
return ( return (
<div className="h-full w-full"> <div className="h-full w-full">
<div className="flex h-full w-full flex-col overflow-hidden"> <div className="flex h-full w-full flex-col overflow-hidden">
<Navbar className="h-auto flex-shrink-0"> <NavbarMain className="h-auto flex-shrink-0">
<NavbarCenter>{t('discover.title')}</NavbarCenter> <NavbarCenter>{t('discover.title')}</NavbarCenter>
</Navbar> </NavbarMain>
{categories.length > 0 && ( {categories.length > 0 && (
<div className="px-4 py-2"> <div className="px-4 py-2">

View File

@ -1,6 +1,5 @@
import { Navbar } from '@renderer/components/app/Navbar' import { Navbar } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import FloatingSidebar from '@renderer/components/Popups/FloatingSidebar'
import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover' import MinAppsPopover from '@renderer/components/Popups/MinAppsPopover'
import SearchPopup from '@renderer/components/Popups/SearchPopup' import SearchPopup from '@renderer/components/Popups/SearchPopup'
import { isLinux, isMac, isWindows } from '@renderer/config/constant' import { isLinux, isMac, isWindows } from '@renderer/config/constant'
@ -13,11 +12,11 @@ import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService' import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setNarrowMode } from '@renderer/store/settings' import { setNarrowMode } from '@renderer/store/settings'
import { Assistant, Topic } from '@renderer/types' import { Assistant } from '@renderer/types'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { t } from 'i18next' import { t } from 'i18next'
import { LayoutGrid, PanelLeftClose, PanelRightClose, Search } from 'lucide-react' import { LayoutGrid, PanelLeft, PanelRight, Search } from 'lucide-react'
import { FC, useCallback, useState } from 'react' import { FC, useCallback } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import SelectModelButton from './components/SelectModelButton' import SelectModelButton from './components/SelectModelButton'
@ -25,27 +24,22 @@ import UpdateAppButton from './components/UpdateAppButton'
interface Props { interface Props {
activeAssistant: Assistant activeAssistant: Assistant
activeTopic: Topic
setActiveTopic: (topic: Topic) => void
setActiveAssistant: (assistant: Assistant) => void
position: 'left' | 'right' position: 'left' | 'right'
} }
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTopic, setActiveTopic }) => { const ChatNavbar: FC<Props> = ({ activeAssistant }) => {
const { assistant } = useAssistant(activeAssistant.id) const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants() const { showAssistants, toggleShowAssistants } = useShowAssistants()
const isFullscreen = useFullscreen() const isFullscreen = useFullscreen()
const { topicPosition, sidebarIcons, narrowMode } = useSettings() const { topicPosition, sidebarIcons, narrowMode } = useSettings()
const { showTopics, toggleShowTopics } = useShowTopics() const { toggleShowTopics } = useShowTopics()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const [sidebarHideCooldown, setSidebarHideCooldown] = useState(false)
// Function to toggle assistants with cooldown // Function to toggle assistants with cooldown
const handleToggleShowAssistants = useCallback(() => { const handleToggleShowAssistants = useCallback(() => {
if (showAssistants) { if (showAssistants) {
// When hiding sidebar, set cooldown // When hiding sidebar, set cooldown
toggleShowAssistants() toggleShowAssistants()
setSidebarHideCooldown(true)
// setTimeout(() => { // setTimeout(() => {
// setSidebarHideCooldown(false) // setSidebarHideCooldown(false)
// }, 10000) // 10 seconds cooldown // }, 10000) // 10 seconds cooldown
@ -55,20 +49,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
} }
}, [showAssistants, 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_assistants', handleToggleShowAssistants)
useShortcut('toggle_show_topics', () => { useShortcut('toggle_show_topics', () => {
@ -95,17 +75,19 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
<NavbarIcon <NavbarIcon
onClick={() => toggleShowAssistants()} onClick={() => toggleShowAssistants()}
style={{ marginRight: 8, marginLeft: isMac && !isFullscreen ? 4 : -12 }}> style={{ marginRight: 8, marginLeft: isMac && !isFullscreen ? 4 : -12 }}>
{showAssistants ? <PanelLeftClose size={18} /> : <PanelRightClose size={18} />} {showAssistants ? <PanelLeft size={18} /> : <PanelRight size={18} />}
</NavbarIcon> </NavbarIcon>
<SelectModelButton assistant={assistant} /> <SelectModelButton assistant={assistant} />
</HStack> </HStack>
<HStack alignItems="center" gap={8}> <HStack alignItems="center" gap={8}>
<UpdateAppButton /> <UpdateAppButton />
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}> {isMac && (
<NarrowIcon onClick={() => SearchPopup.show()}> <Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
<Search size={18} /> <NarrowIcon onClick={() => SearchPopup.show()}>
</NarrowIcon> <Search size={18} />
</Tooltip> </NarrowIcon>
</Tooltip>
)}
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}> <Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
<NarrowIcon onClick={handleNarrowModeToggle}> <NarrowIcon onClick={handleNarrowModeToggle}>
<i className="iconfont icon-icon-adaptive-width"></i> <i className="iconfont icon-icon-adaptive-width"></i>
@ -120,34 +102,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
</Tooltip> </Tooltip>
</MinAppsPopover> </MinAppsPopover>
)} )}
{topicPosition === 'right' && !showTopics && !sidebarHideCooldown && (
<FloatingSidebar
activeAssistant={assistant}
setActiveAssistant={setActiveAssistant}
activeTopic={activeTopic}
setActiveTopic={setActiveTopic}
position={'right'}>
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}>
<NavbarIcon onClick={() => toggleShowTopics()}>
<PanelLeftClose size={18} />
</NavbarIcon>
</Tooltip>
</FloatingSidebar>
)}
{topicPosition === 'right' && !showTopics && sidebarHideCooldown && (
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}>
<NavbarIcon onClick={() => toggleShowTopics()} onMouseOut={() => setSidebarHideCooldown(false)}>
<PanelLeftClose size={18} />
</NavbarIcon>
</Tooltip>
)}
{topicPosition === 'right' && showTopics && (
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={2}>
<NavbarIcon onClick={() => handleToggleShowTopics()}>
<PanelRightClose size={18} />
</NavbarIcon>
</Tooltip>
)}
</HStack> </HStack>
</NavbarContainer> </NavbarContainer>
</Navbar> </Navbar>
@ -163,7 +117,7 @@ const NavbarContainer = styled.div<{ $isFullscreen: boolean; $showSidebar: boole
max-height: var(--navbar-height); max-height: var(--navbar-height);
min-height: var(--navbar-height); min-height: var(--navbar-height);
justify-content: space-between; justify-content: space-between;
padding-left: ${({ $showSidebar }) => (isMac && !$showSidebar ? '70px' : '10px')}; padding-left: ${({ $showSidebar }) => (isMac ? ($showSidebar ? '10px' : '75px') : '15px')};
font-weight: bold; font-weight: bold;
color: var(--color-text-1); color: var(--color-text-1);
padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '140px' : isLinux ? '120px' : '12px')}; padding-right: ${({ $isFullscreen }) => ($isFullscreen ? '12px' : isWindows ? '140px' : isLinux ? '120px' : '12px')};
@ -210,4 +164,4 @@ const NarrowIcon = styled(NavbarIcon)`
} }
` `
export default HeaderNavbar export default ChatNavbar

View File

@ -8,7 +8,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import Chat from './Chat' import Chat from './Chat'
import Navbar from './Navbar' import ChatNavbar from './ChatNavbar'
const HomePage: FC = () => { const HomePage: FC = () => {
const { assistants } = useAssistants() const { assistants } = useAssistants()
@ -54,13 +54,7 @@ const HomePage: FC = () => {
return ( return (
<Container id="home-page"> <Container id="home-page">
<Navbar <ChatNavbar activeAssistant={activeAssistant} position="left" />
activeAssistant={activeAssistant}
activeTopic={activeTopic}
setActiveTopic={setActiveTopic}
setActiveAssistant={setActiveAssistant}
position="left"
/>
<ContentContainer id="content-container"> <ContentContainer id="content-container">
<Chat <Chat
assistant={activeAssistant} assistant={activeAssistant}

View File

@ -14,7 +14,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useKnowledgeBases } from '@renderer/hooks/useKnowledge' import { useKnowledgeBases } from '@renderer/hooks/useKnowledge'
import { useMCPServers } from '@renderer/hooks/useMCPServers' import { useMCPServers } from '@renderer/hooks/useMCPServers'
import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations' import { useMessageOperations, useTopicLoading } from '@renderer/hooks/useMessageOperations'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { useRuntime } from '@renderer/hooks/useRuntime'
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings' import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts' import { useShortcut, useShortcutDisplay } from '@renderer/hooks/useShortcuts'
import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon' import { useSidebarIconShow } from '@renderer/hooks/useSidebarIcon'
@ -52,6 +52,7 @@ import InputbarTools, { InputbarToolsRef } from './InputbarTools'
import KnowledgeBaseInput from './KnowledgeBaseInput' import KnowledgeBaseInput from './KnowledgeBaseInput'
import MentionModelsInput from './MentionModelsInput' import MentionModelsInput from './MentionModelsInput'
import SendMessageButton from './SendMessageButton' import SendMessageButton from './SendMessageButton'
import SettingButton from './SettingButton'
import TokenCount from './TokenCount' import TokenCount from './TokenCount'
interface Props { interface Props {
@ -405,8 +406,6 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
} }
const addNewTopic = useCallback(async () => { const addNewTopic = useCallback(async () => {
await modelGenerating()
const topic = getDefaultTopic(assistant.id) const topic = getDefaultTopic(assistant.id)
await db.topics.add({ id: topic.id, messages: [] }) await db.topics.add({ id: topic.id, messages: [] })
@ -858,6 +857,7 @@ const Inputbar: FC<Props> = ({ assistant: _assistant, setActiveTopic, topic }) =
ToolbarButton={ToolbarButton} ToolbarButton={ToolbarButton}
onClick={onNewContext} onClick={onNewContext}
/> />
<SettingButton assistant={assistant} ToolbarButton={ToolbarButton} />
<TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} /> <TranslateButton text={text} onTranslated={onTranslated} isLoading={isTranslating} />
{loading && ( {loading && (
<Tooltip placement="top" title={t('chat.input.pause')} arrow> <Tooltip placement="top" title={t('chat.input.pause')} arrow>

View File

@ -0,0 +1,43 @@
import { Assistant } from '@renderer/types'
import { Popover } from 'antd'
import { Settings } from 'lucide-react'
import { FC, useState } from 'react'
import SettingsTab from '../Tabs/SettingsTab'
interface Props {
assistant: Assistant
ToolbarButton: any
}
const SettingButton: FC<Props> = ({ assistant, ToolbarButton }) => {
const [open, setOpen] = useState(false)
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen)
}
const handleClose = () => {
setOpen(false)
}
return (
<Popover
placement="topLeft"
content={<SettingsTab assistant={assistant} onClose={handleClose} />}
trigger="click"
open={open}
onOpenChange={handleOpenChange}
styles={{
body: {
padding: '4px 2px 4px 2px'
}
}}>
<ToolbarButton type="text">
<Settings size={18} />
</ToolbarButton>
</Popover>
)
}
export default SettingButton

View File

@ -80,7 +80,7 @@ const Assistants: FC<AssistantsTabProps> = ({
if (assistantsTabSortType === 'tags') { if (assistantsTabSortType === 'tags') {
return ( return (
<Container className="assistants-tab" ref={containerRef}> <Container className="assistants-tab" ref={containerRef}>
<div style={{ marginBottom: 4 }}> <div style={{ display: 'flex', flexDirection: 'column', marginBottom: 4, gap: 10 }}>
{getGroupedAssistants.map((group) => ( {getGroupedAssistants.map((group) => (
<TagsContainer key={group.tag}> <TagsContainer key={group.tag}>
{group.tag !== t('assistants.tags.untagged') && ( {group.tag !== t('assistants.tags.untagged') && (
@ -197,12 +197,7 @@ const AssistantAddItem = styled.div`
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: var(--color-background-soft); background-color: var(--color-list-item-hover);
}
&.active {
background-color: var(--color-background-soft);
border: 0.5px solid var(--color-border);
} }
` `
@ -228,7 +223,7 @@ const GroupTitleName = styled.div`
color: var(--color-text); color: var(--color-text);
font-size: 13px; font-size: 13px;
line-height: 24px; line-height: 24px;
margin-right: 2px; margin-right: 5px;
display: flex; display: flex;
` `

View File

@ -69,6 +69,7 @@ import OpenAISettingsGroup from './components/OpenAISettingsGroup'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
onClose: () => void
} }
const SettingsTab: FC<Props> = (props) => { const SettingsTab: FC<Props> = (props) => {
@ -197,7 +198,10 @@ const SettingsTab: FC<Props> = (props) => {
type="text" type="text"
size="small" size="small"
icon={<Settings2 size={16} />} icon={<Settings2 size={16} />}
onClick={() => AssistantSettingsPopup.show({ assistant, tab: 'model' })} onClick={() => {
AssistantSettingsPopup.show({ assistant, tab: 'model' })
props.onClose()
}}
/> />
</HStack> </HStack>
}> }>
@ -681,8 +685,10 @@ const SettingsTab: FC<Props> = (props) => {
} }
const Container = styled(Scrollbar)` const Container = styled(Scrollbar)`
min-width: 300px;
max-width: 40vw;
max-height: 70vh;
display: flex; display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
padding: 0 8px; padding: 0 8px;
padding-right: 0; padding-right: 0;

View File

@ -1,12 +1,8 @@
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup' import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
import { Segmented as AntSegmented } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import Assistants from './AssistantsTab' import Assistants from './AssistantsTab'
@ -19,37 +15,14 @@ interface Props {
activeTopic: Topic activeTopic: Topic
setActiveAssistant: (assistant: Assistant) => void setActiveAssistant: (assistant: Assistant) => void
setActiveTopic: (topic: Topic) => void setActiveTopic: (topic: Topic) => void
position: 'left' | 'right'
forceToSeeAllTab?: boolean
style?: React.CSSProperties style?: React.CSSProperties
} }
type Tab = 'assistants' | 'topic' | 'settings' type Tab = 'assistants' | 'topic' | 'settings'
const HomeTabs: FC<Props> = ({ const HomeTabs: FC<Props> = ({ tab, activeAssistant, activeTopic, setActiveAssistant, setActiveTopic, style }) => {
tab,
activeAssistant,
activeTopic,
setActiveAssistant,
setActiveTopic,
position,
forceToSeeAllTab,
style
}) => {
const { addAssistant } = useAssistants() const { addAssistant } = useAssistants()
const { topicPosition } = useSettings()
const { defaultAssistant } = useDefaultAssistant() const { defaultAssistant } = useDefaultAssistant()
const { showTopics, toggleShowTopics } = useShowTopics()
const { t } = useTranslation()
const showTab = !(position === 'left' && topicPosition === 'right')
const assistantTab = {
label: t('assistants.abbr'),
value: 'assistants'
// icon: <BotIcon size={16} />
}
const onCreateAssistant = async () => { const onCreateAssistant = async () => {
const assistant = await AddAssistantPopup.show() const assistant = await AddAssistantPopup.show()
@ -62,36 +35,6 @@ const HomeTabs: FC<Props> = ({
setActiveAssistant(assistant) setActiveAssistant(assistant)
} }
// useEffect(() => {
// const unsubscribes = [
// EventEmitter.on(EVENT_NAMES.SHOW_ASSISTANTS, (): any => {
// showTab && setTab('assistants')
// }),
// EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => {
// showTab && setTab('topic')
// }),
// EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => {
// showTab && setTab('settings')
// }),
// EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => {
// showTab && setTab('topic')
// if (position === 'left' && topicPosition === 'right') {
// toggleShowTopics()
// }
// })
// ]
// return () => unsubscribes.forEach((unsub) => unsub())
// }, [position, showTab, tab, toggleShowTopics, topicPosition])
// useEffect(() => {
// if (position === 'right' && topicPosition === 'right' && tab === 'assistants') {
// setTab('topic')
// }
// if (position === 'left' && topicPosition === 'right' && forceToSeeAllTab != true && tab !== 'assistants') {
// setTab('assistants')
// }
// }, [position, tab, topicPosition, forceToSeeAllTab])
return ( return (
<Container style={{ ...style }} className="home-tabs"> <Container style={{ ...style }} className="home-tabs">
<TabContent className="home-tabs-content"> <TabContent className="home-tabs-content">
@ -134,68 +77,4 @@ const TabContent = styled.div`
overflow-x: hidden; overflow-x: hidden;
` `
const Divider = styled.div`
border-top: 0.5px solid var(--color-border);
margin-top: 10px;
margin-left: 10px;
margin-right: 10px;
`
const Segmented = styled(AntSegmented)`
font-family: var(--font-family);
&.ant-segmented {
background-color: transparent;
margin: 0 10px;
margin-top: 10px;
padding: 0;
}
.ant-segmented-item {
overflow: hidden;
transition: none !important;
height: 34px;
line-height: 34px;
background-color: transparent;
user-select: none;
border-radius: var(--list-item-border-radius);
box-shadow: none;
}
.ant-segmented-item-selected,
.ant-segmented-item-selected:active {
transition: none !important;
background-color: var(--color-list-item);
}
.ant-segmented-item-label {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
font-size: 13px;
height: 100%;
}
.ant-segmented-item-label[aria-selected='true'] {
color: var(--color-text);
}
.icon-business-smart-assistant {
margin-right: -2px;
}
.ant-segmented-thumb {
transition: none !important;
background-color: var(--color-list-item);
border-radius: var(--list-item-border-radius);
box-shadow: none;
&:hover {
background-color: transparent;
}
}
.ant-segmented-item-label,
.ant-segmented-item-icon {
display: flex;
align-items: center;
}
/* These styles ensure the same appearance as before */
border-radius: 0;
box-shadow: none;
`
export default HomeTabs export default HomeTabs

View File

@ -1,7 +1,4 @@
import { NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { isLinux, isWindows } from '@renderer/config/constant'
import { useFullscreen } from '@renderer/hooks/useFullscreen'
import { Button, Dropdown, Menu, type MenuProps } from 'antd' import { Button, Dropdown, Menu, type MenuProps } from 'antd'
import { ChevronDown, Search } from 'lucide-react' import { ChevronDown, Search } from 'lucide-react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -74,29 +71,27 @@ export const McpSettingsNavbar = () => {
})) }))
return ( return (
<NavbarRight style={{ paddingRight: useFullscreen() ? '12px' : isWindows ? 150 : isLinux ? 120 : 12 }}> <HStack alignItems="center" gap={5}>
<HStack alignItems="center" gap={5}> <Button
size="small"
type="text"
onClick={() => navigate('/mcp-servers/npx-search')}
icon={<Search size={14} />}
className="nodrag"
style={{ fontSize: 13, height: 28, borderRadius: 20 }}>
{t('settings.mcp.searchNpx')}
</Button>
<Dropdown menu={{ items: resourceMenuItems }} trigger={['click']}>
<Button <Button
size="small" size="small"
type="text" type="text"
onClick={() => navigate('/mcp-servers/npx-search')}
icon={<Search size={14} />}
className="nodrag" className="nodrag"
style={{ fontSize: 13, height: 28, borderRadius: 20 }}> style={{ fontSize: 13, height: 28, borderRadius: 20, display: 'flex', alignItems: 'center' }}>
{t('settings.mcp.searchNpx')} {t('settings.mcp.findMore')}
<ChevronDown size={16} />
</Button> </Button>
<Dropdown menu={{ items: resourceMenuItems }} trigger={['click']}> </Dropdown>
<Button <InstallNpxUv mini />
size="small" </HStack>
type="text"
className="nodrag"
style={{ fontSize: 13, height: 28, borderRadius: 20, display: 'flex', alignItems: 'center' }}>
{t('settings.mcp.findMore')}
<ChevronDown size={16} />
</Button>
</Dropdown>
<InstallNpxUv mini />
</HStack>
</NavbarRight>
) )
} }