mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-20 23:22:05 +08:00
fix: adjust Navbar and Chat components for better layout and responsiveness
- Updated Navbar styles to improve margin handling for macOS. - Refactored Chat component to streamline layout and enhance responsiveness, including adjustments to main height calculations and navbar integration. - Cleaned up commented code and improved the structure of the ChatNavbar for better clarity and maintainability. - Enhanced styling in various components for consistent appearance and behavior across different screen sizes.
This commit is contained in:
parent
8470e252d6
commit
d4b1db0407
@ -67,14 +67,14 @@ const NavbarContainer = styled.div<{ $isFullScreen: boolean }>`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
min-height: ${isMac ? 'env(titlebar-area-height)' : 'var(--navbar-height)'};
|
min-height: ${isMac ? 'env(titlebar-area-height)' : 'var(--navbar-height)'};
|
||||||
max-height: var(--navbar-height);
|
max-height: var(--navbar-height);
|
||||||
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0};
|
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1 + 2px)' : 0};
|
||||||
padding-left: ${({ $isFullScreen }) =>
|
padding-left: ${({ $isFullScreen }) =>
|
||||||
isMac ? ($isFullScreen ? 'var(--sidebar-width)' : 'env(titlebar-area-x)') : 0};
|
isMac ? ($isFullScreen ? 'var(--sidebar-width)' : 'env(titlebar-area-x)') : 0};
|
||||||
-webkit-app-region: drag;
|
-webkit-app-region: drag;
|
||||||
`
|
`
|
||||||
|
|
||||||
const NavbarLeftContainer = styled.div`
|
const NavbarLeftContainer = styled.div`
|
||||||
min-width: ${isMac ? 'calc(var(--assistants-width) - 20px)' : 'var(--assistants-width)'};
|
/* min-width: ${isMac ? 'calc(var(--assistants-width) - 20px)' : 'var(--assistants-width)'}; */
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@ -140,9 +140,7 @@ const Chat: FC<Props> = (props) => {
|
|||||||
firstUpdateOrNoFirstUpdateHandler()
|
firstUpdateOrNoFirstUpdateHandler()
|
||||||
}
|
}
|
||||||
|
|
||||||
const mainHeight = isTopNavbar
|
const mainHeight = isTopNavbar ? 'calc(100vh - var(--navbar-height) - 6px)' : 'calc(100vh - var(--navbar-height))'
|
||||||
? 'calc(100vh - var(--navbar-height) - var(--navbar-height) - 12px)'
|
|
||||||
: 'calc(100vh - var(--navbar-height))'
|
|
||||||
|
|
||||||
const SessionMessages = useMemo(() => {
|
const SessionMessages = useMemo(() => {
|
||||||
if (activeAgentId === null) {
|
if (activeAgentId === null) {
|
||||||
@ -192,66 +190,84 @@ const Chat: FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container id="chat" className={classNames([messageStyle, { 'multi-select-mode': isMultiSelectMode }])}>
|
<Container id="chat" className={classNames([messageStyle, { 'multi-select-mode': isMultiSelectMode }])}>
|
||||||
{isTopNavbar && (
|
|
||||||
<ChatNavbar
|
|
||||||
activeAssistant={props.assistant}
|
|
||||||
activeTopic={props.activeTopic}
|
|
||||||
setActiveTopic={props.setActiveTopic}
|
|
||||||
setActiveAssistant={props.setActiveAssistant}
|
|
||||||
position="left"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<HStack>
|
<HStack>
|
||||||
<Main
|
<motion.div
|
||||||
ref={mainRef}
|
animate={{
|
||||||
id="chat-main"
|
marginRight: topicPosition === 'right' && showTopics ? 'var(--assistants-width)' : 0
|
||||||
vertical
|
}}
|
||||||
flex={1}
|
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||||
justify="space-between"
|
style={{ flex: 1, display: 'flex', minWidth: 0 }}>
|
||||||
style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
|
<Main
|
||||||
<QuickPanelProvider>
|
ref={mainRef}
|
||||||
{activeTopicOrSession === 'topic' && (
|
id="chat-main"
|
||||||
<>
|
vertical
|
||||||
<Messages
|
flex={1}
|
||||||
key={props.activeTopic.id}
|
justify="space-between"
|
||||||
assistant={assistant}
|
style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
|
||||||
topic={props.activeTopic}
|
<QuickPanelProvider>
|
||||||
setActiveTopic={props.setActiveTopic}
|
<ChatNavbar
|
||||||
onComponentUpdate={messagesComponentUpdateHandler}
|
activeAssistant={props.assistant}
|
||||||
onFirstUpdate={messagesComponentFirstUpdateHandler}
|
activeTopic={props.activeTopic}
|
||||||
/>
|
setActiveTopic={props.setActiveTopic}
|
||||||
<ContentSearch
|
setActiveAssistant={props.setActiveAssistant}
|
||||||
ref={contentSearchRef}
|
position="left"
|
||||||
searchTarget={mainRef as React.RefObject<HTMLElement>}
|
/>
|
||||||
filter={contentSearchFilter}
|
<div
|
||||||
includeUser={filterIncludeUser}
|
className="flex flex-1 flex-col justify-between"
|
||||||
onIncludeUserChange={userOutlinedItemClickHandler}
|
style={{ height: `calc(${mainHeight} - var(--navbar-height))` }}>
|
||||||
/>
|
{activeTopicOrSession === 'topic' && (
|
||||||
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
<>
|
||||||
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
|
<Messages
|
||||||
</>
|
key={props.activeTopic.id}
|
||||||
)}
|
assistant={assistant}
|
||||||
{activeTopicOrSession === 'session' && !activeAgentId && <AgentInvalid />}
|
topic={props.activeTopic}
|
||||||
{activeTopicOrSession === 'session' && activeAgentId && !activeSessionId && <SessionInvalid />}
|
setActiveTopic={props.setActiveTopic}
|
||||||
{activeTopicOrSession === 'session' && activeAgentId && activeSessionId && (
|
onComponentUpdate={messagesComponentUpdateHandler}
|
||||||
<>
|
onFirstUpdate={messagesComponentFirstUpdateHandler}
|
||||||
<SessionMessages />
|
/>
|
||||||
<SessionInputBar />
|
<ContentSearch
|
||||||
</>
|
ref={contentSearchRef}
|
||||||
)}
|
searchTarget={mainRef as React.RefObject<HTMLElement>}
|
||||||
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
|
filter={contentSearchFilter}
|
||||||
</QuickPanelProvider>
|
includeUser={filterIncludeUser}
|
||||||
</Main>
|
onIncludeUserChange={userOutlinedItemClickHandler}
|
||||||
|
/>
|
||||||
|
{messageNavigation === 'buttons' && <ChatNavigation containerId="messages" />}
|
||||||
|
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} topic={props.activeTopic} />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeTopicOrSession === 'session' && !activeAgentId && <AgentInvalid />}
|
||||||
|
{activeTopicOrSession === 'session' && activeAgentId && !activeSessionId && <SessionInvalid />}
|
||||||
|
{activeTopicOrSession === 'session' && activeAgentId && activeSessionId && (
|
||||||
|
<>
|
||||||
|
<SessionMessages />
|
||||||
|
<SessionInputBar />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
|
||||||
|
</div>
|
||||||
|
</QuickPanelProvider>
|
||||||
|
</Main>
|
||||||
|
</motion.div>
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{topicPosition === 'right' && showTopics && (
|
{topicPosition === 'right' && showTopics && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ width: 0, opacity: 0 }}
|
key="right-tabs"
|
||||||
animate={{ width: 'auto', opacity: 1 }}
|
initial={{ x: 'var(--assistants-width)' }}
|
||||||
exit={{ width: 0, opacity: 0 }}
|
animate={{ x: 0 }}
|
||||||
|
exit={{ x: 'var(--assistants-width)' }}
|
||||||
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
transition={{ duration: 0.3, ease: 'easeInOut' }}
|
||||||
style={{ overflow: 'hidden' }}>
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
right: 0,
|
||||||
|
top: isTopNavbar ? 0 : 'calc(var(--navbar-height) + 1px)',
|
||||||
|
width: 'var(--assistants-width)',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: 10
|
||||||
|
}}>
|
||||||
<Tabs
|
<Tabs
|
||||||
activeAssistant={assistant}
|
activeAssistant={assistant}
|
||||||
activeTopic={props.activeTopic}
|
activeTopic={props.activeTopic}
|
||||||
@ -269,13 +285,14 @@ const Chat: FC<Props> = (props) => {
|
|||||||
|
|
||||||
export const useChatMaxWidth = () => {
|
export const useChatMaxWidth = () => {
|
||||||
const { showTopics, topicPosition } = useSettings()
|
const { showTopics, topicPosition } = useSettings()
|
||||||
const { isLeftNavbar } = useNavbarPosition()
|
const { isLeftNavbar, isTopNavbar } = useNavbarPosition()
|
||||||
const { showAssistants } = useShowAssistants()
|
const { showAssistants } = useShowAssistants()
|
||||||
const showRightTopics = showTopics && topicPosition === 'right'
|
const showRightTopics = showTopics && topicPosition === 'right'
|
||||||
const minusAssistantsWidth = showAssistants ? '- var(--assistants-width)' : ''
|
const minusAssistantsWidth = showAssistants ? '- var(--assistants-width)' : ''
|
||||||
const minusRightTopicsWidth = showRightTopics ? '- var(--assistants-width)' : ''
|
const minusRightTopicsWidth = showRightTopics ? '- var(--assistants-width)' : ''
|
||||||
|
const minusBorderWidth = isTopNavbar ? (showTopics ? '- 12px' : '- 6px') : ''
|
||||||
const sidebarWidth = isLeftNavbar ? '- var(--sidebar-width)' : ''
|
const sidebarWidth = isLeftNavbar ? '- var(--sidebar-width)' : ''
|
||||||
return `calc(100vw ${sidebarWidth} ${minusAssistantsWidth} ${minusRightTopicsWidth})`
|
return `calc(100vw ${sidebarWidth} ${minusAssistantsWidth} ${minusRightTopicsWidth} ${minusBorderWidth})`
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { HStack } from '@renderer/components/Layout'
|
|||||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useNavbarPosition, useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||||
@ -34,6 +34,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||||
const { topicPosition, narrowMode } = useSettings()
|
const { topicPosition, narrowMode } = useSettings()
|
||||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||||
|
const { isTopNavbar } = useNavbarPosition()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
||||||
@ -73,16 +74,16 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
// )
|
// )
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavbarHeader className="home-navbar">
|
<NavbarHeader className="home-navbar" style={{ height: 'var(--navbar-height)' }}>
|
||||||
<div className="flex min-w-0 flex-1 shrink items-center overflow-auto">
|
<div className="flex h-full min-w-0 flex-1 shrink items-center overflow-auto">
|
||||||
{showAssistants && (
|
{isTopNavbar && showAssistants && (
|
||||||
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={0.8}>
|
||||||
<NavbarIcon onClick={toggleShowAssistants}>
|
<NavbarIcon onClick={toggleShowAssistants}>
|
||||||
<PanelLeftClose size={18} />
|
<PanelLeftClose size={18} />
|
||||||
</NavbarIcon>
|
</NavbarIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{!showAssistants && (
|
{isTopNavbar && !showAssistants && (
|
||||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
||||||
<NavbarIcon onClick={() => toggleShowAssistants()} style={{ marginRight: 8 }}>
|
<NavbarIcon onClick={() => toggleShowAssistants()} style={{ marginRight: 8 }}>
|
||||||
<PanelRightClose size={18} />
|
<PanelRightClose size={18} />
|
||||||
@ -90,13 +91,13 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<AnimatePresence initial={false}>
|
<AnimatePresence initial={false}>
|
||||||
{!showAssistants && (
|
{!showAssistants && isTopNavbar && (
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ width: 0, opacity: 0 }}
|
initial={{ width: 0, opacity: 0 }}
|
||||||
animate={{ width: 'auto', opacity: 1 }}
|
animate={{ width: 'auto', opacity: 1 }}
|
||||||
exit={{ width: 0, opacity: 0 }}
|
exit={{ width: 0, opacity: 0 }}
|
||||||
transition={{ duration: 0.3, ease: 'easeInOut' }}>
|
transition={{ duration: 0.3, ease: 'easeInOut' }}>
|
||||||
<NavbarIcon onClick={onShowAssistantsDrawer} style={{ marginRight: 8 }}>
|
<NavbarIcon onClick={onShowAssistantsDrawer} style={{ marginRight: 5 }}>
|
||||||
<Menu size={18} />
|
<Menu size={18} />
|
||||||
</NavbarIcon>
|
</NavbarIcon>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
@ -105,25 +106,29 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
|||||||
<ChatNavbarContent assistant={assistant} />
|
<ChatNavbarContent assistant={assistant} />
|
||||||
</div>
|
</div>
|
||||||
<HStack alignItems="center" gap={8}>
|
<HStack alignItems="center" gap={8}>
|
||||||
<UpdateAppButton />
|
{isTopNavbar && <UpdateAppButton />}
|
||||||
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
|
{isTopNavbar && (
|
||||||
<NarrowIcon onClick={handleNarrowModeToggle}>
|
<Tooltip title={t('navbar.expand')} mouseEnterDelay={0.8}>
|
||||||
<i className="iconfont icon-icon-adaptive-width"></i>
|
<NarrowIcon onClick={handleNarrowModeToggle}>
|
||||||
</NarrowIcon>
|
<i className="iconfont icon-icon-adaptive-width"></i>
|
||||||
</Tooltip>
|
</NarrowIcon>
|
||||||
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
</Tooltip>
|
||||||
<NavbarIcon onClick={() => SearchPopup.show()}>
|
)}
|
||||||
<Search size={18} />
|
{isTopNavbar && (
|
||||||
</NavbarIcon>
|
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
|
||||||
</Tooltip>
|
<NavbarIcon onClick={() => SearchPopup.show()}>
|
||||||
{topicPosition === 'right' && !showTopics && (
|
<Search size={18} />
|
||||||
|
</NavbarIcon>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{isTopNavbar && topicPosition === 'right' && !showTopics && (
|
||||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}>
|
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={2}>
|
||||||
<NavbarIcon onClick={toggleShowTopics}>
|
<NavbarIcon onClick={toggleShowTopics}>
|
||||||
<PanelLeftClose size={18} />
|
<PanelLeftClose size={18} />
|
||||||
</NavbarIcon>
|
</NavbarIcon>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{topicPosition === 'right' && showTopics && (
|
{isTopNavbar && topicPosition === 'right' && showTopics && (
|
||||||
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={2}>
|
<Tooltip title={t('navbar.hide_sidebar')} mouseEnterDelay={2}>
|
||||||
<NavbarIcon onClick={toggleShowTopics}>
|
<NavbarIcon onClick={toggleShowTopics}>
|
||||||
<PanelRightClose size={18} />
|
<PanelRightClose size={18} />
|
||||||
|
|||||||
@ -1,14 +1,11 @@
|
|||||||
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
import { isLinux, isWin } from '@renderer/config/constant'
|
||||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
|
||||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { useChatMaxWidth } from '@renderer/pages/home/Chat'
|
|
||||||
import ChatNavbarContent from '@renderer/pages/home/components/ChatNavbarContent'
|
|
||||||
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'
|
||||||
@ -17,11 +14,10 @@ import { Tooltip } from 'antd'
|
|||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
||||||
import { AnimatePresence, motion } from 'motion/react'
|
import { AnimatePresence, motion } from 'motion/react'
|
||||||
import React, { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AssistantsDrawer from './components/AssistantsDrawer'
|
import AssistantsDrawer from './components/AssistantsDrawer'
|
||||||
import SelectModelButton from './components/SelectModelButton'
|
|
||||||
import UpdateAppButton from './components/UpdateAppButton'
|
import UpdateAppButton from './components/UpdateAppButton'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -40,11 +36,9 @@ const HeaderNavbar: FC<Props> = ({
|
|||||||
setActiveTopic,
|
setActiveTopic,
|
||||||
activeTopicOrSession
|
activeTopicOrSession
|
||||||
}) => {
|
}) => {
|
||||||
const { assistant } = useAssistant(activeAssistant.id)
|
|
||||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||||
const { topicPosition, narrowMode } = useSettings()
|
const { topicPosition, narrowMode } = useSettings()
|
||||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||||
const chatMaxWidth = useChatMaxWidth()
|
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
||||||
@ -101,7 +95,7 @@ const HeaderNavbar: FC<Props> = ({
|
|||||||
justifyContent: 'flex-start',
|
justifyContent: 'flex-start',
|
||||||
borderRight: 'none',
|
borderRight: 'none',
|
||||||
paddingLeft: 0,
|
paddingLeft: 0,
|
||||||
paddingRight: 10,
|
paddingRight: 0,
|
||||||
minWidth: 'auto'
|
minWidth: 'auto'
|
||||||
}}>
|
}}>
|
||||||
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
<Tooltip title={t('navbar.show_sidebar')} mouseEnterDelay={0.8}>
|
||||||
@ -123,22 +117,7 @@ const HeaderNavbar: FC<Props> = ({
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</NavbarLeft>
|
</NavbarLeft>
|
||||||
)}
|
)}
|
||||||
<NavbarCenter>
|
<NavbarCenter></NavbarCenter>
|
||||||
{activeTopicOrSession === 'topic' ? (
|
|
||||||
<HStack alignItems="center" gap={6} ml={!isMac ? 16 : 0}>
|
|
||||||
<SelectModelButton assistant={assistant} />
|
|
||||||
</HStack>
|
|
||||||
) : (
|
|
||||||
<ChatNavbarContainer
|
|
||||||
style={{
|
|
||||||
maxWidth: chatMaxWidth,
|
|
||||||
marginLeft: !isMac ? 16 : 0
|
|
||||||
}}>
|
|
||||||
<ChatNavbarContent assistant={assistant} />
|
|
||||||
</ChatNavbarContainer>
|
|
||||||
)}
|
|
||||||
</NavbarCenter>
|
|
||||||
|
|
||||||
<NavbarRight
|
<NavbarRight
|
||||||
style={{
|
style={{
|
||||||
justifyContent: 'flex-end',
|
justifyContent: 'flex-end',
|
||||||
@ -220,15 +199,4 @@ const NarrowIcon = styled(NavbarIcon)`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
const ChatNavbarContainer: React.FC<{ children: React.ReactNode; style?: React.CSSProperties }> = ({
|
|
||||||
children,
|
|
||||||
style
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<div className="nodrag flex min-w-0 flex-1 items-center justify-start gap-1.5 overflow-hidden" style={style}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HeaderNavbar
|
export default HeaderNavbar
|
||||||
|
|||||||
@ -180,7 +180,7 @@ const AssistantsTab: FC<AssistantsTabProps> = (props) => {
|
|||||||
const Container = styled(Scrollbar)`
|
const Container = styled(Scrollbar)`
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 10px;
|
padding: 12px 10px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default AssistantsTab
|
export default AssistantsTab
|
||||||
|
|||||||
@ -30,7 +30,7 @@ const SessionSettingsTab: FC<Props> = ({ session, update }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-[var(--assistants-width)] p-2 px-3 pt-4">
|
<div className="w-[var(--assistants-width)] p-2 px-3 pt-4">
|
||||||
<EssentialSettings agentBase={session} update={update} />
|
<EssentialSettings agentBase={session} update={update} showModelSetting={false} />
|
||||||
<AdvancedSettings agentBase={session} update={update} />
|
<AdvancedSettings agentBase={session} update={update} />
|
||||||
<Divider className="my-2" />
|
<Divider className="my-2" />
|
||||||
<Button size="sm" fullWidth onPress={onMoreSetting}>
|
<Button size="sm" fullWidth onPress={onMoreSetting}>
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const SessionsTab: FC<SessionsTabProps> = () => {
|
|||||||
const { activeAgentId } = chat
|
const { activeAgentId } = chat
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { apiServer } = useSettings()
|
const { apiServer } = useSettings()
|
||||||
const { topicPosition, navbarPosition } = useSettings()
|
|
||||||
|
|
||||||
if (!apiServer.enabled) {
|
if (!apiServer.enabled) {
|
||||||
return (
|
return (
|
||||||
@ -34,15 +33,7 @@ const SessionsTab: FC<SessionsTabProps> = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AnimatePresence mode="wait">
|
<AnimatePresence mode="wait">
|
||||||
<motion.div
|
<motion.div className={cn('overflow-hidden', 'h-full')}>
|
||||||
initial={{ width: 0, opacity: 0 }}
|
|
||||||
animate={{ width: 'var(--assistants-width)', opacity: 1 }}
|
|
||||||
exit={{ width: 0, opacity: 0 }}
|
|
||||||
transition={{ duration: 0.5, ease: 'easeInOut' }}
|
|
||||||
className={cn(
|
|
||||||
'overflow-hidden',
|
|
||||||
topicPosition === 'right' && navbarPosition === 'top' ? 'rounded-l-2xl border-t border-b border-l' : undefined
|
|
||||||
)}>
|
|
||||||
<Sessions agentId={activeAgentId} />
|
<Sessions agentId={activeAgentId} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|||||||
@ -44,9 +44,16 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
|
|||||||
<AgentLabel agent={agent} />
|
<AgentLabel agent={agent} />
|
||||||
</AgentNameWrapper>
|
</AgentNameWrapper>
|
||||||
</AssistantNameRow>
|
</AssistantNameRow>
|
||||||
<MenuButton>
|
{isActive && (
|
||||||
{isActive ? <SessionCount>{sessions.length}</SessionCount> : <Bot size={14} className="text-primary" />}
|
<MenuButton>
|
||||||
</MenuButton>
|
<SessionCount>{sessions.length}</SessionCount>
|
||||||
|
</MenuButton>
|
||||||
|
)}
|
||||||
|
{!isActive && (
|
||||||
|
<BotIcon>
|
||||||
|
<Bot size={16} className="text-primary" />
|
||||||
|
</BotIcon>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
@ -110,6 +117,16 @@ export const MenuButton: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ cla
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const BotIcon: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'absolute top-[8px] right-[12px] flex flex-row items-center justify-center rounded-full text-[14px] text-[var(--color-text)]',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
export const SessionCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
export const SessionCount: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { Button, cn, Input, Tooltip } from '@heroui/react'
|
|
||||||
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
import { DeleteIcon, EditIcon } from '@renderer/components/Icons'
|
||||||
import { isMac } from '@renderer/config/constant'
|
import { isMac } from '@renderer/config/constant'
|
||||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||||
@ -11,9 +10,19 @@ import { SessionLabel } from '@renderer/pages/settings/AgentSettings/shared'
|
|||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import { newMessagesActions } from '@renderer/store/newMessage'
|
import { newMessagesActions } from '@renderer/store/newMessage'
|
||||||
import { AgentSessionEntity } from '@renderer/types'
|
import { AgentSessionEntity } from '@renderer/types'
|
||||||
import { ContextMenu, ContextMenuContent, ContextMenuItem, ContextMenuTrigger } from '@renderer/ui/context-menu'
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuContent,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuSub,
|
||||||
|
ContextMenuSubContent,
|
||||||
|
ContextMenuSubTrigger,
|
||||||
|
ContextMenuTrigger
|
||||||
|
} from '@renderer/ui/context-menu'
|
||||||
|
import { classNames } from '@renderer/utils'
|
||||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||||
import { XIcon } from 'lucide-react'
|
import { Tooltip } from 'antd'
|
||||||
|
import { MenuIcon, XIcon } from 'lucide-react'
|
||||||
import React, { FC, memo, startTransition, useEffect, useMemo, useState } from 'react'
|
import React, { FC, memo, startTransition, useEffect, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -24,13 +33,11 @@ interface SessionItemProps {
|
|||||||
session: AgentSessionEntity
|
session: AgentSessionEntity
|
||||||
// use external agentId as SSOT, instead of session.agent_id
|
// use external agentId as SSOT, instead of session.agent_id
|
||||||
agentId: string
|
agentId: string
|
||||||
isDisabled?: boolean
|
|
||||||
isLoading?: boolean
|
|
||||||
onDelete: () => void
|
onDelete: () => void
|
||||||
onPress: () => void
|
onPress: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoading, onDelete, onPress }) => {
|
const SessionItem: FC<SessionItemProps> = ({ session, agentId, onDelete, onPress }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { chat } = useRuntime()
|
const { chat } = useRuntime()
|
||||||
const { updateSession } = useUpdateSession(agentId)
|
const { updateSession } = useUpdateSession(agentId)
|
||||||
@ -50,16 +57,16 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
const DeleteButton = () => {
|
const DeleteButton = () => {
|
||||||
return (
|
return (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
content={t('chat.topics.delete.shortcut', { key: isMac ? '⌘' : 'Ctrl' })}
|
placement="bottom"
|
||||||
classNames={{ content: 'text-xs' }}
|
mouseEnterDelay={0.7}
|
||||||
delay={500}
|
mouseLeaveDelay={0}
|
||||||
closeDelay={0}>
|
title={
|
||||||
<div
|
<div style={{ fontSize: '12px', opacity: 0.8, fontStyle: 'italic' }}>
|
||||||
role="button"
|
{t('chat.topics.delete.shortcut', { key: isMac ? '⌘' : 'Ctrl' })}
|
||||||
className={cn(
|
</div>
|
||||||
'mr-2 flex aspect-square h-6 w-6 items-center justify-center rounded-2xl',
|
}>
|
||||||
isConfirmingDeletion ? 'hover:bg-danger-100' : 'hover:bg-foreground-300'
|
<MenuButton
|
||||||
)}
|
className="menu"
|
||||||
onClick={(e: React.MouseEvent) => {
|
onClick={(e: React.MouseEvent) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
if (isConfirmingDeletion || e.ctrlKey || e.metaKey) {
|
if (isConfirmingDeletion || e.ctrlKey || e.metaKey) {
|
||||||
@ -78,17 +85,11 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
}
|
}
|
||||||
}}>
|
}}>
|
||||||
{isConfirmingDeletion ? (
|
{isConfirmingDeletion ? (
|
||||||
<DeleteIcon
|
<DeleteIcon size={14} color="var(--color-error)" style={{ pointerEvents: 'none' }} />
|
||||||
size={14}
|
|
||||||
className="opacity-0 transition-colors-opacity group-hover:text-danger group-hover:opacity-100"
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<XIcon
|
<XIcon size={14} color="var(--color-text-3)" style={{ pointerEvents: 'none' }} />
|
||||||
size={14}
|
|
||||||
className={cn(isActive ? 'opacity-100' : 'opacity-0', 'group-hover:opacity-100', 'transition-opacity')}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</MenuButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -106,44 +107,44 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
}
|
}
|
||||||
}, [activeSessionId, dispatch, isFulfilled, session.id, sessionTopicId])
|
}, [activeSessionId, dispatch, isFulfilled, session.id, sessionTopicId])
|
||||||
|
|
||||||
|
const { topicPosition, setTopicPosition } = useSettings()
|
||||||
|
const singlealone = topicPosition === 'right'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ContextMenu modal={false}>
|
<ContextMenu modal={false}>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<ButtonContainer
|
<SessionListItem
|
||||||
isDisabled={isDisabled}
|
className={classNames(isActive ? 'active' : '', singlealone ? 'singlealone' : '')}
|
||||||
isLoading={isLoading}
|
onClick={isEditing ? undefined : onPress}
|
||||||
onPress={onPress}
|
|
||||||
isActive={isActive}
|
|
||||||
onDoubleClick={() => startEdit(session.name ?? '')}
|
onDoubleClick={() => startEdit(session.name ?? '')}
|
||||||
className="group">
|
title={session.name ?? session.id}
|
||||||
<SessionLabelContainer className="name h-full w-full pl-1" title={session.name ?? session.id}>
|
style={{
|
||||||
{isPending && !isActive && <PendingIndicator />}
|
borderRadius: 'var(--list-item-border-radius)',
|
||||||
{isFulfilled && !isActive && <FulfilledIndicator />}
|
cursor: isEditing ? 'default' : 'pointer'
|
||||||
{isEditing && (
|
}}>
|
||||||
<Input
|
{isPending && !isActive && <PendingIndicator />}
|
||||||
|
{isFulfilled && !isActive && <FulfilledIndicator />}
|
||||||
|
<SessionNameContainer>
|
||||||
|
{isEditing ? (
|
||||||
|
<SessionEditInput
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
variant="bordered"
|
|
||||||
value={editValue}
|
value={editValue}
|
||||||
onValueChange={handleValueChange}
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleValueChange(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e: React.MouseEvent) => e.stopPropagation()}
|
||||||
classNames={{
|
style={{ opacity: isSaving ? 0.5 : 1 }}
|
||||||
base: 'h-full',
|
|
||||||
mainWrapper: 'h-full',
|
|
||||||
inputWrapper: 'h-full min-h-0 px-1.5',
|
|
||||||
input: isSaving ? 'brightness-50' : undefined
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
) : (
|
||||||
{!isEditing && (
|
<>
|
||||||
<div className="flex w-full items-center justify-between">
|
<SessionName>
|
||||||
<SessionLabel session={session} />
|
<SessionLabel session={session} />
|
||||||
|
</SessionName>
|
||||||
<DeleteButton />
|
<DeleteButton />
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</SessionLabelContainer>
|
</SessionNameContainer>
|
||||||
</ButtonContainer>
|
</SessionListItem>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
@ -157,6 +158,20 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
<EditIcon size={14} />
|
<EditIcon size={14} />
|
||||||
{t('common.edit')}
|
{t('common.edit')}
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
|
<ContextMenuSub>
|
||||||
|
<ContextMenuSubTrigger className="gap-2">
|
||||||
|
<MenuIcon size={14} />
|
||||||
|
{t('settings.topic.position.label')}
|
||||||
|
</ContextMenuSubTrigger>
|
||||||
|
<ContextMenuSubContent>
|
||||||
|
<ContextMenuItem key="left" onClick={() => setTopicPosition('left')}>
|
||||||
|
{t('settings.topic.position.left')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
<ContextMenuItem key="right" onClick={() => setTopicPosition('right')}>
|
||||||
|
{t('settings.topic.position.right')}
|
||||||
|
</ContextMenuItem>
|
||||||
|
</ContextMenuSubContent>
|
||||||
|
</ContextMenuSub>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
key="delete"
|
key="delete"
|
||||||
className="text-danger"
|
className="text-danger"
|
||||||
@ -172,38 +187,96 @@ const SessionItem: FC<SessionItemProps> = ({ session, agentId, isDisabled, isLoa
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const ButtonContainer: React.FC<React.ComponentProps<typeof Button> & { isActive?: boolean }> = ({
|
const SessionListItem = styled.div`
|
||||||
isActive,
|
padding: 7px 12px;
|
||||||
className,
|
border-radius: var(--list-item-border-radius);
|
||||||
children,
|
font-size: 13px;
|
||||||
...props
|
display: flex;
|
||||||
}) => {
|
flex-direction: column;
|
||||||
const { topicPosition } = useSettings()
|
justify-content: space-between;
|
||||||
const activeBg = topicPosition === 'left' ? 'bg-[var(--color-list-item)]' : 'bg-foreground-100'
|
cursor: pointer;
|
||||||
return (
|
width: calc(var(--assistants-width) - 20px);
|
||||||
<Button
|
margin-bottom: 8px;
|
||||||
{...props}
|
|
||||||
variant="light"
|
|
||||||
className={cn(
|
|
||||||
'relative mb-2 flex h-9 flex-row justify-between p-0',
|
|
||||||
'rounded-[var(--list-item-border-radius)]',
|
|
||||||
'border-[0.5px] border-transparent',
|
|
||||||
'w-[calc(var(--assistants-width)_-_20px)]',
|
|
||||||
'cursor-pointer',
|
|
||||||
isActive ? cn(activeBg, 'shadow-sm') : undefined,
|
|
||||||
className
|
|
||||||
)}>
|
|
||||||
{children}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const SessionLabelContainer: React.FC<React.HTMLAttributes<HTMLDivElement>> = ({ className, ...props }) => (
|
.menu {
|
||||||
<div
|
opacity: 0;
|
||||||
{...props}
|
color: var(--color-text-3);
|
||||||
className={cn('text-[13px] text-[var(--color-text)]', 'flex flex-row items-center gap-2', className)}
|
}
|
||||||
/>
|
|
||||||
)
|
&:hover {
|
||||||
|
background-color: var(--color-list-item-hover);
|
||||||
|
transition: background-color 0.1s;
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: var(--color-list-item);
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
.menu {
|
||||||
|
opacity: 1;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.singlealone {
|
||||||
|
border-radius: 0 !important;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
}
|
||||||
|
&.active {
|
||||||
|
border-left: 2px solid var(--color-primary);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const SessionNameContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
height: 20px;
|
||||||
|
justify-content: space-between;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SessionName = styled.div`
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 13px;
|
||||||
|
position: relative;
|
||||||
|
`
|
||||||
|
|
||||||
|
const SessionEditInput = styled.input`
|
||||||
|
background: var(--color-background);
|
||||||
|
border: none;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: inherit;
|
||||||
|
padding: 2px 6px;
|
||||||
|
width: 100%;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
`
|
||||||
|
|
||||||
|
const MenuButton = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 20px;
|
||||||
|
min-height: 20px;
|
||||||
|
.anticon {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
const PendingIndicator = styled.div.attrs({
|
const PendingIndicator = styled.div.attrs({
|
||||||
className: 'animation-pulse'
|
className: 'animation-pulse'
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
} from '@renderer/store/runtime'
|
} from '@renderer/store/runtime'
|
||||||
import { CreateSessionForm } from '@renderer/types'
|
import { CreateSessionForm } from '@renderer/types'
|
||||||
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
import { buildAgentSessionTopicId } from '@renderer/utils/agentSession'
|
||||||
import { AnimatePresence, motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { memo, useCallback, useEffect } from 'react'
|
import { memo, useCallback, useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
|
|||||||
const { agent } = useAgent(agentId)
|
const { agent } = useAgent(agentId)
|
||||||
const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId)
|
const { sessions, isLoading, error, deleteSession, createSession } = useSessions(agentId)
|
||||||
const { chat } = useRuntime()
|
const { chat } = useRuntime()
|
||||||
const { activeSessionIdMap, sessionWaiting } = chat
|
const { activeSessionIdMap } = chat
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const setActiveSessionId = useCallback(
|
const setActiveSessionId = useCallback(
|
||||||
@ -109,45 +109,30 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
|
|||||||
if (error) return <Alert color="danger" content={t('agent.session.get.error.failed')} />
|
if (error) return <Alert color="danger" content={t('agent.session.get.error.failed')} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div className="sessions-tab flex h-full w-full flex-col p-2">
|
||||||
initial={{ opacity: 0 }}
|
<AddButton onPress={handleCreateSession} className="mb-2">
|
||||||
animate={{ opacity: 1 }}
|
{t('agent.session.add.title')}
|
||||||
transition={{ duration: 0.3 }}
|
</AddButton>
|
||||||
className="sessions-tab flex h-full w-full flex-col p-2">
|
{/* h-9 */}
|
||||||
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
|
<DynamicVirtualList
|
||||||
<AddButton onPress={handleCreateSession} className="mb-2">
|
list={sessions}
|
||||||
{t('agent.session.add.title')}
|
estimateSize={() => 9 * 4}
|
||||||
</AddButton>
|
scrollerStyle={{
|
||||||
</motion.div>
|
// FIXME: This component only supports CSSProperties
|
||||||
<AnimatePresence>
|
overflowX: 'hidden'
|
||||||
{/* h-9 */}
|
}}
|
||||||
<DynamicVirtualList
|
autoHideScrollbar>
|
||||||
list={sessions}
|
{(session) => (
|
||||||
estimateSize={() => 9 * 4}
|
<SessionItem
|
||||||
scrollerStyle={{
|
key={session.id}
|
||||||
// FIXME: This component only supports CSSProperties
|
session={session}
|
||||||
overflowX: 'hidden'
|
agentId={agentId}
|
||||||
}}
|
onDelete={() => handleDeleteSession(session.id)}
|
||||||
autoHideScrollbar>
|
onPress={() => setActiveSessionId(agentId, session.id)}
|
||||||
{(session) => (
|
/>
|
||||||
<motion.div
|
)}
|
||||||
key={session.id}
|
</DynamicVirtualList>
|
||||||
initial={{ opacity: 0, x: 20 }}
|
</div>
|
||||||
animate={{ opacity: 1, x: 0 }}
|
|
||||||
transition={{ duration: 0.3 }}>
|
|
||||||
<SessionItem
|
|
||||||
session={session}
|
|
||||||
agentId={agentId}
|
|
||||||
isDisabled={sessionWaiting[session.id]}
|
|
||||||
isLoading={sessionWaiting[session.id]}
|
|
||||||
onDelete={() => handleDeleteSession(session.id)}
|
|
||||||
onPress={() => setActiveSessionId(agentId, session.id)}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
)}
|
|
||||||
</DynamicVirtualList>
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -189,10 +189,7 @@ const Container = styled.div`
|
|||||||
background-color: var(--color-background);
|
background-color: var(--color-background);
|
||||||
}
|
}
|
||||||
[navbar-position='top'] & {
|
[navbar-position='top'] & {
|
||||||
height: calc(100vh - var(--navbar-height) - 12px);
|
height: calc(100vh - var(--navbar-height));
|
||||||
&.right {
|
|
||||||
height: calc(100vh - var(--navbar-height) - var(--navbar-height) - 12px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
.collapsed {
|
.collapsed {
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { BreadcrumbItem, Breadcrumbs, Chip, cn } from '@heroui/react'
|
import { BreadcrumbItem, Breadcrumbs, cn } from '@heroui/react'
|
||||||
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
||||||
import { permissionModeCards } from '@renderer/constants/permissionModes'
|
|
||||||
import { useActiveAgent } from '@renderer/hooks/agents/useActiveAgent'
|
import { useActiveAgent } from '@renderer/hooks/agents/useActiveAgent'
|
||||||
import { useActiveSession } from '@renderer/hooks/agents/useActiveSession'
|
import { useActiveSession } from '@renderer/hooks/agents/useActiveSession'
|
||||||
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
||||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||||
import { AgentEntity, AgentSessionEntity, ApiModel, Assistant, PermissionMode } from '@renderer/types'
|
import { AgentEntity, AgentSessionEntity, ApiModel, Assistant } from '@renderer/types'
|
||||||
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
||||||
import { t } from 'i18next'
|
import { t } from 'i18next'
|
||||||
|
import { Folder } from 'lucide-react'
|
||||||
import { FC, ReactNode, useCallback } from 'react'
|
import { FC, ReactNode, useCallback } from 'react'
|
||||||
|
|
||||||
import { AgentSettingsPopup, SessionSettingsPopup } from '../../settings/AgentSettings'
|
import { AgentSettingsPopup, SessionSettingsPopup } from '../../settings/AgentSettings'
|
||||||
@ -38,24 +38,15 @@ const ChatNavbarContent: FC<Props> = ({ assistant }) => {
|
|||||||
<>
|
<>
|
||||||
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
|
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
|
||||||
{activeTopicOrSession === 'session' && activeAgent && (
|
{activeTopicOrSession === 'session' && activeAgent && (
|
||||||
<HorizontalScrollContainer>
|
<HorizontalScrollContainer className="ml-2 flex-initial">
|
||||||
<Breadcrumbs
|
<Breadcrumbs classNames={{ base: 'flex', list: 'flex-nowrap' }}>
|
||||||
classNames={{
|
|
||||||
base: 'flex',
|
|
||||||
list: 'flex-nowrap'
|
|
||||||
}}>
|
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
onPress={() => AgentSettingsPopup.show({ agentId: activeAgent.id })}
|
onPress={() => AgentSettingsPopup.show({ agentId: activeAgent.id })}
|
||||||
classNames={{
|
classNames={{ base: 'self-stretch', item: 'h-full' }}>
|
||||||
base: 'self-stretch',
|
<AgentLabel
|
||||||
item: 'h-full'
|
agent={activeAgent}
|
||||||
}}>
|
classNames={{ name: 'max-w-40 text-xs', avatar: 'h-4.5 w-4.5', container: 'gap-1.5' }}
|
||||||
<Chip size="md" variant="light" className="h-full transition-background hover:bg-foreground-100">
|
/>
|
||||||
<AgentLabel
|
|
||||||
agent={activeAgent}
|
|
||||||
classNames={{ name: 'max-w-40 font-bold text-xs', avatar: 'h-4.5 w-4.5', container: 'gap-1.5' }}
|
|
||||||
/>
|
|
||||||
</Chip>
|
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
{activeSession && (
|
{activeSession && (
|
||||||
<BreadcrumbItem
|
<BreadcrumbItem
|
||||||
@ -65,13 +56,8 @@ const ChatNavbarContent: FC<Props> = ({ assistant }) => {
|
|||||||
sessionId: activeSession.id
|
sessionId: activeSession.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
classNames={{
|
classNames={{ base: 'self-stretch', item: 'h-full' }}>
|
||||||
base: 'self-stretch',
|
<SessionLabel session={activeSession} className="max-w-40 text-xs" />
|
||||||
item: 'h-full'
|
|
||||||
}}>
|
|
||||||
<Chip size="md" variant="light" className="h-full transition-background hover:bg-foreground-100">
|
|
||||||
<SessionLabel session={activeSession} className="max-w-40 font-bold text-xs" />
|
|
||||||
</Chip>
|
|
||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
)}
|
)}
|
||||||
{activeSession && (
|
{activeSession && (
|
||||||
@ -97,11 +83,11 @@ const SessionWorkspaceMeta: FC<{ agent: AgentEntity; session: AgentSessionEntity
|
|||||||
}
|
}
|
||||||
|
|
||||||
const firstAccessiblePath = session.accessible_paths?.[0]
|
const firstAccessiblePath = session.accessible_paths?.[0]
|
||||||
const permissionMode = (session.configuration?.permission_mode ?? 'default') as PermissionMode
|
// const permissionMode = (session.configuration?.permission_mode ?? 'default') as PermissionMode
|
||||||
const permissionModeCard = permissionModeCards.find((card) => card.mode === permissionMode)
|
// const permissionModeCard = permissionModeCards.find((card) => card.mode === permissionMode)
|
||||||
const permissionModeLabel = permissionModeCard
|
// const permissionModeLabel = permissionModeCard
|
||||||
? t(permissionModeCard.titleKey, permissionModeCard.titleFallback)
|
// ? t(permissionModeCard.titleKey, permissionModeCard.titleFallback)
|
||||||
: permissionMode
|
// : permissionMode
|
||||||
|
|
||||||
const infoItems: ReactNode[] = []
|
const infoItems: ReactNode[] = []
|
||||||
|
|
||||||
@ -117,12 +103,13 @@ const SessionWorkspaceMeta: FC<{ agent: AgentEntity; session: AgentSessionEntity
|
|||||||
}) => (
|
}) => (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'rounded-medium border border-default-200 px-2 py-1 text-foreground-500 text-xs dark:text-foreground-400',
|
'flex items-center gap-1.5 text-foreground-500 text-xs dark:text-foreground-400',
|
||||||
onClick !== undefined ? 'cursor-pointer' : undefined,
|
onClick !== undefined ? 'cursor-pointer' : undefined,
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
title={text}
|
title={text}
|
||||||
onClick={onClick}>
|
onClick={onClick}>
|
||||||
|
<Folder className="h-3.5 w-3.5 shrink-0" />
|
||||||
<span className="block truncate">{text}</span>
|
<span className="block truncate">{text}</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@ -148,7 +135,7 @@ const SessionWorkspaceMeta: FC<{ agent: AgentEntity; session: AgentSessionEntity
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
infoItems.push(<InfoTag key="permission-mode" text={permissionModeLabel} className="max-w-50" />)
|
// infoItems.push(<InfoTag key="permission-mode" text={permissionModeLabel} className="max-w-50" />)
|
||||||
|
|
||||||
if (infoItems.length === 0) {
|
if (infoItems.length === 0) {
|
||||||
return null
|
return null
|
||||||
|
|||||||
@ -38,12 +38,12 @@ const SelectAgentBaseModelButton: FC<Props> = ({ agentBase: agent, onSelect, isD
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="light"
|
variant="light"
|
||||||
className="nodrag rounded-2xl px-1 py-3"
|
className="nodrag h-[28px] rounded-2xl px-1"
|
||||||
onPress={onSelectModel}
|
onPress={onSelectModel}
|
||||||
isDisabled={isDisabled}>
|
isDisabled={isDisabled}>
|
||||||
<div className="flex items-center gap-1.5 overflow-x-hidden">
|
<div className="flex items-center gap-1.5 overflow-x-hidden">
|
||||||
<ModelAvatar model={model ? apiModelAdapter(model) : undefined} size={20} />
|
<ModelAvatar model={model ? apiModelAdapter(model) : undefined} size={20} />
|
||||||
<span className="truncate font-medium">
|
<span className="truncate text-[var(--color-text)]">
|
||||||
{model ? model.name : t('button.select_model')} {providerName ? ' | ' + providerName : ''}
|
{model ? model.name : t('button.select_model')} {providerName ? ' | ' + providerName : ''}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -87,6 +87,7 @@ const ButtonContent = styled.div`
|
|||||||
const ModelName = styled.span`
|
const ModelName = styled.span`
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-right: -2px;
|
margin-right: -2px;
|
||||||
|
font-size: 12px;
|
||||||
`
|
`
|
||||||
|
|
||||||
export default SelectModelButton
|
export default SelectModelButton
|
||||||
|
|||||||
@ -104,7 +104,7 @@ const AgentSettingPopupContainer: React.FC<AgentSettingPopupParams> = ({ tab, ag
|
|||||||
afterClose={afterClose}
|
afterClose={afterClose}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
footer={null}
|
footer={null}
|
||||||
title={<AgentLabel agent={agent} classNames={{ name: 'text-lg font-extrabold' }} />}
|
title={<AgentLabel agent={agent} />}
|
||||||
transitionName="animation-move-down"
|
transitionName="animation-move-down"
|
||||||
styles={{
|
styles={{
|
||||||
content: {
|
content: {
|
||||||
|
|||||||
@ -20,13 +20,15 @@ type EssentialSettingsProps =
|
|||||||
| {
|
| {
|
||||||
agentBase: GetAgentResponse | undefined | null
|
agentBase: GetAgentResponse | undefined | null
|
||||||
update: ReturnType<typeof useUpdateAgent>['updateAgent']
|
update: ReturnType<typeof useUpdateAgent>['updateAgent']
|
||||||
|
showModelSetting?: boolean
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
agentBase: GetAgentSessionResponse | undefined | null
|
agentBase: GetAgentSessionResponse | undefined | null
|
||||||
update: ReturnType<typeof useUpdateSession>['updateSession']
|
update: ReturnType<typeof useUpdateSession>['updateSession']
|
||||||
|
showModelSetting?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update }) => {
|
const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update, showModelSetting = true }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
if (!agentBase) return null
|
if (!agentBase) return null
|
||||||
@ -46,7 +48,7 @@ const EssentialSettings: FC<EssentialSettingsProps> = ({ agentBase, update }) =>
|
|||||||
)}
|
)}
|
||||||
{isAgent && <AvatarSetting agent={agentBase} update={update} />}
|
{isAgent && <AvatarSetting agent={agentBase} update={update} />}
|
||||||
<NameSetting base={agentBase} update={update} />
|
<NameSetting base={agentBase} update={update} />
|
||||||
<ModelSetting base={agentBase} update={update} />
|
{showModelSetting && <ModelSetting base={agentBase} update={update} />}
|
||||||
<AccessibleDirsSetting base={agentBase} update={update} />
|
<AccessibleDirsSetting base={agentBase} update={update} />
|
||||||
<DescriptionSetting base={agentBase} update={update} />
|
<DescriptionSetting base={agentBase} update={update} />
|
||||||
</SettingsContainer>
|
</SettingsContainer>
|
||||||
|
|||||||
@ -106,7 +106,7 @@ const SessionSettingPopupContainer: React.FC<SessionSettingPopupParams> = ({ tab
|
|||||||
afterClose={afterClose}
|
afterClose={afterClose}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
footer={null}
|
footer={null}
|
||||||
title={<SessionLabel session={session} className="font-extrabold text-lg" />}
|
title={<SessionLabel session={session} />}
|
||||||
transitionName="animation-move-down"
|
transitionName="animation-move-down"
|
||||||
styles={{
|
styles={{
|
||||||
content: {
|
content: {
|
||||||
|
|||||||
@ -36,7 +36,7 @@ export const AgentLabel: React.FC<AgentLabelProps> = ({ agent, classNames }) =>
|
|||||||
return (
|
return (
|
||||||
<div className={cn('flex w-full items-center gap-2 truncate', classNames?.container)}>
|
<div className={cn('flex w-full items-center gap-2 truncate', classNames?.container)}>
|
||||||
<EmojiIcon emoji={emoji || '⭐️'} className={classNames?.avatar} />
|
<EmojiIcon emoji={emoji || '⭐️'} className={classNames?.avatar} />
|
||||||
<span className={cn('truncate', classNames?.name)}>
|
<span className={cn('truncate', 'text-[var(--color-text)]', classNames?.name)}>
|
||||||
{agent?.name ?? (agent?.type ? getAgentTypeLabel(agent.type) : '')}
|
{agent?.name ?? (agent?.type ? getAgentTypeLabel(agent.type) : '')}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -52,7 +52,7 @@ export const SessionLabel: React.FC<SessionLabelProps> = ({ session, className }
|
|||||||
const displayName = session?.name ?? session?.id
|
const displayName = session?.name ?? session?.id
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<span className={cn('truncate px-2 text-sm', className)}>{displayName}</span>
|
<span className={cn('truncate text-[var(--color-text)] text-sm', className)}>{displayName}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user