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:
kangfenmao 2025-10-18 00:12:38 +08:00
parent 8470e252d6
commit d4b1db0407
18 changed files with 343 additions and 300 deletions

View File

@ -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;

View File

@ -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,18 +190,16 @@ 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>
<motion.div
animate={{
marginRight: topicPosition === 'right' && showTopics ? 'var(--assistants-width)' : 0
}}
transition={{ duration: 0.3, ease: 'easeInOut' }}
style={{ flex: 1, display: 'flex', minWidth: 0 }}>
<Main <Main
ref={mainRef} ref={mainRef}
id="chat-main" id="chat-main"
@ -212,6 +208,16 @@ const Chat: FC<Props> = (props) => {
justify="space-between" justify="space-between"
style={{ maxWidth: chatMaxWidth, height: mainHeight }}> style={{ maxWidth: chatMaxWidth, height: mainHeight }}>
<QuickPanelProvider> <QuickPanelProvider>
<ChatNavbar
activeAssistant={props.assistant}
activeTopic={props.activeTopic}
setActiveTopic={props.setActiveTopic}
setActiveAssistant={props.setActiveAssistant}
position="left"
/>
<div
className="flex flex-1 flex-col justify-between"
style={{ height: `calc(${mainHeight} - var(--navbar-height))` }}>
{activeTopicOrSession === 'topic' && ( {activeTopicOrSession === 'topic' && (
<> <>
<Messages <Messages
@ -242,16 +248,26 @@ const Chat: FC<Props> = (props) => {
</> </>
)} )}
{isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />} {isMultiSelectMode && <MultiSelectActionPopup topic={props.activeTopic} />}
</div>
</QuickPanelProvider> </QuickPanelProvider>
</Main> </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`

View File

@ -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 />}
{isTopNavbar && (
<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>
</NarrowIcon> </NarrowIcon>
</Tooltip> </Tooltip>
)}
{isTopNavbar && (
<Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}> <Tooltip title={t('chat.assistant.search.placeholder')} mouseEnterDelay={0.8}>
<NavbarIcon onClick={() => SearchPopup.show()}> <NavbarIcon onClick={() => SearchPopup.show()}>
<Search size={18} /> <Search size={18} />
</NavbarIcon> </NavbarIcon>
</Tooltip> </Tooltip>
{topicPosition === 'right' && !showTopics && ( )}
{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} />

View File

@ -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

View File

@ -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

View File

@ -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}>

View File

@ -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>

View File

@ -44,9 +44,16 @@ const AgentItem: FC<AgentItemProps> = ({ agent, isActive, onDelete, onPress }) =
<AgentLabel agent={agent} /> <AgentLabel agent={agent} />
</AgentNameWrapper> </AgentNameWrapper>
</AssistantNameRow> </AssistantNameRow>
{isActive && (
<MenuButton> <MenuButton>
{isActive ? <SessionCount>{sessions.length}</SessionCount> : <Bot size={14} className="text-primary" />} <SessionCount>{sessions.length}</SessionCount>
</MenuButton> </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(

View File

@ -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={{
borderRadius: 'var(--list-item-border-radius)',
cursor: isEditing ? 'default' : 'pointer'
}}>
{isPending && !isActive && <PendingIndicator />} {isPending && !isActive && <PendingIndicator />}
{isFulfilled && !isActive && <FulfilledIndicator />} {isFulfilled && !isActive && <FulfilledIndicator />}
{isEditing && ( <SessionNameContainer>
<Input {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" .menu {
className={cn( opacity: 0;
'relative mb-2 flex h-9 flex-row justify-between p-0', color: var(--color-text-3);
'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 }) => ( &:hover {
<div background-color: var(--color-list-item-hover);
{...props} transition: background-color 0.1s;
className={cn('text-[13px] text-[var(--color-text)]', 'flex flex-row items-center gap-2', className)}
/> .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'

View File

@ -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,17 +109,10 @@ 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 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
className="sessions-tab flex h-full w-full flex-col p-2">
<motion.div initial={{ opacity: 0, y: -10 }} animate={{ opacity: 1, y: 0 }}>
<AddButton onPress={handleCreateSession} className="mb-2"> <AddButton onPress={handleCreateSession} className="mb-2">
{t('agent.session.add.title')} {t('agent.session.add.title')}
</AddButton> </AddButton>
</motion.div>
<AnimatePresence>
{/* h-9 */} {/* h-9 */}
<DynamicVirtualList <DynamicVirtualList
list={sessions} list={sessions}
@ -130,24 +123,16 @@ const Sessions: React.FC<SessionsProps> = ({ agentId }) => {
}} }}
autoHideScrollbar> autoHideScrollbar>
{(session) => ( {(session) => (
<motion.div
key={session.id}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.3 }}>
<SessionItem <SessionItem
key={session.id}
session={session} session={session}
agentId={agentId} agentId={agentId}
isDisabled={sessionWaiting[session.id]}
isLoading={sessionWaiting[session.id]}
onDelete={() => handleDeleteSession(session.id)} onDelete={() => handleDeleteSession(session.id)}
onPress={() => setActiveSessionId(agentId, session.id)} onPress={() => setActiveSessionId(agentId, session.id)}
/> />
</motion.div>
)} )}
</DynamicVirtualList> </DynamicVirtualList>
</AnimatePresence> </div>
</motion.div>
) )
} }

View File

@ -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 {

View File

@ -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',
item: 'h-full'
}}>
<Chip size="md" variant="light" className="h-full transition-background hover:bg-foreground-100">
<AgentLabel <AgentLabel
agent={activeAgent} agent={activeAgent}
classNames={{ name: 'max-w-40 font-bold text-xs', avatar: 'h-4.5 w-4.5', container: 'gap-1.5' }} classNames={{ name: 'max-w-40 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

View File

@ -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>

View File

@ -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

View File

@ -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: {

View File

@ -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>

View File

@ -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: {

View File

@ -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>
</> </>
) )
} }