mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 06:30:10 +08:00
fix: agents show ChatNavbar in both LeftNavbar and TopNavbar layouts (#10718)
* fix: show ChatNavbar in both LeftNavbar and TopNavbar layouts
* Revert "fix: show ChatNavbar in both LeftNavbar and TopNavbar layouts"
This reverts commit 7f205bf241.
* refactor: extract ChatNavBarContent from ChatNavBar
* fix: add navbar content to top nav in left nav mode
* fix: add nodrag to navbar container
* fix: lint error
* fix: ChatNavbarContainer layout
* fix: adjust NavbarLeftContainer min-width for macOS compatibility
---------
Co-authored-by: kangfenmao <kangfenmao@qq.com>
This commit is contained in:
parent
a290ee7f39
commit
1c2ce7e0aa
@ -74,7 +74,7 @@ const NavbarContainer = styled.div<{ $isFullScreen: boolean }>`
|
||||
`
|
||||
|
||||
const NavbarLeftContainer = styled.div`
|
||||
min-width: var(--assistants-width);
|
||||
min-width: ${isMac ? 'calc(var(--assistants-width) - 20px)' : 'var(--assistants-width)'};
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@ -1,34 +1,24 @@
|
||||
import { BreadcrumbItem, Breadcrumbs, Chip, cn } from '@heroui/react'
|
||||
import { NavbarHeader } from '@renderer/components/app/Navbar'
|
||||
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import { permissionModeCards } from '@renderer/constants/permissionModes'
|
||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import { useSession } from '@renderer/hooks/agents/useSession'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import { setNarrowMode } from '@renderer/store/settings'
|
||||
import { ApiModel, Assistant, PermissionMode, Topic } from '@renderer/types'
|
||||
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
||||
import { Assistant, Topic } from '@renderer/types'
|
||||
import { Tooltip } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
||||
import { AnimatePresence, motion } from 'motion/react'
|
||||
import React, { FC, ReactNode, useCallback } from 'react'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import { AgentSettingsPopup } from '../settings/AgentSettings'
|
||||
import { AgentLabel } from '../settings/AgentSettings/shared'
|
||||
import AssistantsDrawer from './components/AssistantsDrawer'
|
||||
import SelectAgentModelButton from './components/SelectAgentModelButton'
|
||||
import SelectModelButton from './components/SelectModelButton'
|
||||
import ChatNavbarContent from './components/ChatNavbarContent'
|
||||
import UpdateAppButton from './components/UpdateAppButton'
|
||||
|
||||
interface Props {
|
||||
@ -45,11 +35,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
const { topicPosition, narrowMode } = useSettings()
|
||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||
const dispatch = useAppDispatch()
|
||||
const { chat } = useRuntime()
|
||||
const { activeTopicOrSession, activeAgentId } = chat
|
||||
const sessionId = activeAgentId ? (chat.activeSessionId[activeAgentId] ?? null) : null
|
||||
const { agent } = useAgent(activeAgentId)
|
||||
const { updateModel } = useUpdateAgent()
|
||||
|
||||
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
||||
|
||||
@ -79,14 +64,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
})
|
||||
}
|
||||
|
||||
const handleUpdateModel = useCallback(
|
||||
async (model: ApiModel) => {
|
||||
if (!agent) return
|
||||
return updateModel(agent.id, model.id, { showSuccessToast: false })
|
||||
},
|
||||
[agent, updateModel]
|
||||
)
|
||||
|
||||
return (
|
||||
<NavbarHeader className="home-navbar">
|
||||
<div className="flex min-w-0 flex-1 shrink items-center overflow-auto">
|
||||
@ -117,38 +94,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
|
||||
{activeTopicOrSession === 'session' && agent && (
|
||||
<HorizontalScrollContainer>
|
||||
<Breadcrumbs
|
||||
classNames={{
|
||||
base: 'flex',
|
||||
list: 'flex-nowrap'
|
||||
}}>
|
||||
<BreadcrumbItem
|
||||
onPress={() => AgentSettingsPopup.show({ agentId: agent.id })}
|
||||
classNames={{
|
||||
base: 'self-stretch',
|
||||
item: 'h-full'
|
||||
}}>
|
||||
<Chip size="md" variant="light" className="h-full transition-background hover:bg-foreground-100">
|
||||
<AgentLabel
|
||||
agent={agent}
|
||||
classNames={{ name: 'max-w-50 font-bold text-xs', avatar: 'h-4.5 w-4.5', container: 'gap-1.5' }}
|
||||
/>
|
||||
</Chip>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<SelectAgentModelButton agent={agent} onSelect={handleUpdateModel} />
|
||||
</BreadcrumbItem>
|
||||
{activeAgentId && sessionId && (
|
||||
<BreadcrumbItem>
|
||||
<SessionWorkspaceMeta agentId={activeAgentId} sessionId={sessionId} />
|
||||
</BreadcrumbItem>
|
||||
)}
|
||||
</Breadcrumbs>
|
||||
</HorizontalScrollContainer>
|
||||
)}
|
||||
<ChatNavbarContent assistant={assistant} />
|
||||
</div>
|
||||
<HStack alignItems="center" gap={8}>
|
||||
<UpdateAppButton />
|
||||
@ -181,74 +127,6 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
)
|
||||
}
|
||||
|
||||
const SessionWorkspaceMeta: FC<{ agentId: string; sessionId: string }> = ({ agentId, sessionId }) => {
|
||||
const { agent } = useAgent(agentId)
|
||||
const { session } = useSession(agentId, sessionId)
|
||||
if (!session || !agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
const firstAccessiblePath = session.accessible_paths?.[0]
|
||||
const permissionMode = (session.configuration?.permission_mode ?? 'default') as PermissionMode
|
||||
const permissionModeCard = permissionModeCards.find((card) => card.mode === permissionMode)
|
||||
const permissionModeLabel = permissionModeCard
|
||||
? t(permissionModeCard.titleKey, permissionModeCard.titleFallback)
|
||||
: permissionMode
|
||||
|
||||
const infoItems: ReactNode[] = []
|
||||
|
||||
const InfoTag = ({
|
||||
text,
|
||||
className,
|
||||
onClick
|
||||
}: {
|
||||
text: string
|
||||
className?: string
|
||||
classNames?: {}
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}) => (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-medium border border-default-200 px-2 py-1 text-foreground-500 text-xs dark:text-foreground-400',
|
||||
onClick !== undefined ? 'cursor-pointer' : undefined,
|
||||
className
|
||||
)}
|
||||
title={text}
|
||||
onClick={onClick}>
|
||||
<span className="block truncate">{text}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
// infoItems.push(<InfoTag key="name" text={agent.name ?? ''} className="max-w-60" />)
|
||||
|
||||
if (firstAccessiblePath) {
|
||||
infoItems.push(
|
||||
<InfoTag
|
||||
key="path"
|
||||
text={firstAccessiblePath}
|
||||
className="max-w-60 transition-colors hover:border-primary hover:text-primary"
|
||||
onClick={() => {
|
||||
window.api.file
|
||||
.openPath(firstAccessiblePath)
|
||||
.catch((e) =>
|
||||
window.toast.error(
|
||||
formatErrorMessageWithPrefix(e, t('files.error.open_path', { path: firstAccessiblePath }))
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
infoItems.push(<InfoTag key="permission-mode" text={permissionModeLabel} className="max-w-50" />)
|
||||
|
||||
if (infoItems.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className="ml-2 flex items-center gap-2">{infoItems}</div>
|
||||
}
|
||||
|
||||
export const NavbarIcon = styled.div`
|
||||
-webkit-app-region: none;
|
||||
border-radius: 8px;
|
||||
|
||||
@ -130,6 +130,7 @@ const HomePage: FC = () => {
|
||||
setActiveTopic={setActiveTopic}
|
||||
setActiveAssistant={setActiveAssistant}
|
||||
position="left"
|
||||
activeTopicOrSession={activeTopicOrSession}
|
||||
/>
|
||||
)}
|
||||
<ContentContainer id={isLeftNavbar ? 'content-container' : undefined}>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import SearchPopup from '@renderer/components/Popups/SearchPopup'
|
||||
import { isLinux, isMac, isWin } from '@renderer/config/constant'
|
||||
@ -7,6 +7,8 @@ import { modelGenerating } from '@renderer/hooks/useRuntime'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useShortcut } from '@renderer/hooks/useShortcuts'
|
||||
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 { useAppDispatch } from '@renderer/store'
|
||||
import { setNarrowMode } from '@renderer/store/settings'
|
||||
@ -15,7 +17,7 @@ import { Tooltip } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import { Menu, PanelLeftClose, PanelRightClose, Search } from 'lucide-react'
|
||||
import { AnimatePresence, motion } from 'motion/react'
|
||||
import { FC } from 'react'
|
||||
import React, { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import AssistantsDrawer from './components/AssistantsDrawer'
|
||||
@ -28,13 +30,21 @@ interface Props {
|
||||
setActiveTopic: (topic: Topic) => void
|
||||
setActiveAssistant: (assistant: Assistant) => void
|
||||
position: 'left' | 'right'
|
||||
activeTopicOrSession?: 'topic' | 'session'
|
||||
}
|
||||
|
||||
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTopic, setActiveTopic }) => {
|
||||
const HeaderNavbar: FC<Props> = ({
|
||||
activeAssistant,
|
||||
setActiveAssistant,
|
||||
activeTopic,
|
||||
setActiveTopic,
|
||||
activeTopicOrSession
|
||||
}) => {
|
||||
const { assistant } = useAssistant(activeAssistant.id)
|
||||
const { showAssistants, toggleShowAssistants } = useShowAssistants()
|
||||
const { topicPosition, narrowMode } = useSettings()
|
||||
const { showTopics, toggleShowTopics } = useShowTopics()
|
||||
const chatMaxWidth = useChatMaxWidth()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
useShortcut('toggle_show_assistants', toggleShowAssistants)
|
||||
@ -113,15 +123,29 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, activeTo
|
||||
</AnimatePresence>
|
||||
</NavbarLeft>
|
||||
)}
|
||||
<HStack alignItems="center" gap={6} ml={!isMac ? 16 : 0}>
|
||||
<SelectModelButton assistant={assistant} />
|
||||
</HStack>
|
||||
<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
|
||||
style={{
|
||||
justifyContent: 'flex-end',
|
||||
flex: 1,
|
||||
flex: activeTopicOrSession === 'topic' ? 1 : 'none',
|
||||
position: 'relative',
|
||||
paddingRight: isWin || isLinux ? '144px' : '15px'
|
||||
paddingRight: isWin || isLinux ? '144px' : '15px',
|
||||
minWidth: activeTopicOrSession === 'topic' ? '' : 'auto'
|
||||
}}
|
||||
className="home-navbar-right">
|
||||
<HStack alignItems="center" gap={6}>
|
||||
@ -196,4 +220,15 @@ 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
|
||||
|
||||
136
src/renderer/src/pages/home/components/ChatNavbarContent.tsx
Normal file
136
src/renderer/src/pages/home/components/ChatNavbarContent.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
import { BreadcrumbItem, Breadcrumbs, Chip, cn } from '@heroui/react'
|
||||
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
||||
import { permissionModeCards } from '@renderer/constants/permissionModes'
|
||||
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
||||
import { useSession } from '@renderer/hooks/agents/useSession'
|
||||
import { useUpdateAgent } from '@renderer/hooks/agents/useUpdateAgent'
|
||||
import { useRuntime } from '@renderer/hooks/useRuntime'
|
||||
import { ApiModel, Assistant, PermissionMode } from '@renderer/types'
|
||||
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
||||
import { t } from 'i18next'
|
||||
import { FC, ReactNode, useCallback } from 'react'
|
||||
|
||||
import { AgentSettingsPopup } from '../../settings/AgentSettings'
|
||||
import { AgentLabel } from '../../settings/AgentSettings/shared'
|
||||
import SelectAgentModelButton from './SelectAgentModelButton'
|
||||
import SelectModelButton from './SelectModelButton'
|
||||
|
||||
interface Props {
|
||||
assistant: Assistant
|
||||
}
|
||||
|
||||
const ChatNavbarContent: FC<Props> = ({ assistant }) => {
|
||||
const { chat } = useRuntime()
|
||||
const { activeTopicOrSession, activeAgentId } = chat
|
||||
const sessionId = activeAgentId ? (chat.activeSessionId[activeAgentId] ?? null) : null
|
||||
const { agent } = useAgent(activeAgentId)
|
||||
const { updateModel } = useUpdateAgent()
|
||||
|
||||
const handleUpdateModel = useCallback(
|
||||
async (model: ApiModel) => {
|
||||
if (!agent) return
|
||||
return updateModel(agent.id, model.id, { showSuccessToast: false })
|
||||
},
|
||||
[agent, updateModel]
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
|
||||
{activeTopicOrSession === 'session' && agent && (
|
||||
<HorizontalScrollContainer>
|
||||
<Breadcrumbs classNames={{ base: 'flex', list: 'flex-nowrap' }}>
|
||||
<BreadcrumbItem
|
||||
onPress={() => AgentSettingsPopup.show({ agentId: agent.id })}
|
||||
classNames={{ base: 'self-stretch', item: 'h-full' }}>
|
||||
<Chip size="md" variant="light" className="h-full transition-background hover:bg-foreground-100">
|
||||
<AgentLabel
|
||||
agent={agent}
|
||||
classNames={{ name: 'max-w-50 font-bold text-xs', avatar: 'h-2 w-2 ml-[-4px]', container: 'gap-1.5' }}
|
||||
/>
|
||||
</Chip>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbItem>
|
||||
<SelectAgentModelButton agent={agent} onSelect={handleUpdateModel} />
|
||||
</BreadcrumbItem>
|
||||
{activeAgentId && sessionId && (
|
||||
<BreadcrumbItem>
|
||||
<SessionWorkspaceMeta agentId={activeAgentId} sessionId={sessionId} />
|
||||
</BreadcrumbItem>
|
||||
)}
|
||||
</Breadcrumbs>
|
||||
</HorizontalScrollContainer>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SessionWorkspaceMeta: FC<{ agentId: string; sessionId: string }> = ({ agentId, sessionId }) => {
|
||||
const { agent } = useAgent(agentId)
|
||||
const { session } = useSession(agentId, sessionId)
|
||||
if (!session || !agent) {
|
||||
return null
|
||||
}
|
||||
|
||||
const firstAccessiblePath = session.accessible_paths?.[0]
|
||||
const permissionMode = (session.configuration?.permission_mode ?? 'default') as PermissionMode
|
||||
const permissionModeCard = permissionModeCards.find((card) => card.mode === permissionMode)
|
||||
const permissionModeLabel = permissionModeCard
|
||||
? t(permissionModeCard.titleKey, permissionModeCard.titleFallback)
|
||||
: permissionMode
|
||||
|
||||
const infoItems: ReactNode[] = []
|
||||
|
||||
const InfoTag = ({
|
||||
text,
|
||||
className,
|
||||
onClick
|
||||
}: {
|
||||
text: string
|
||||
className?: string
|
||||
classNames?: {}
|
||||
onClick?: (e: React.MouseEvent) => void
|
||||
}) => (
|
||||
<div
|
||||
className={cn(
|
||||
'rounded-medium border border-default-200 px-2 py-1 text-foreground-500 text-xs dark:text-foreground-400',
|
||||
onClick !== undefined ? 'cursor-pointer' : undefined,
|
||||
className
|
||||
)}
|
||||
title={text}
|
||||
onClick={onClick}>
|
||||
<span className="block truncate">{text}</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
// infoItems.push(<InfoTag key="name" text={agent.name ?? ''} className="max-w-60" />)
|
||||
|
||||
if (firstAccessiblePath) {
|
||||
infoItems.push(
|
||||
<InfoTag
|
||||
key="path"
|
||||
text={firstAccessiblePath}
|
||||
className="max-w-60 transition-colors hover:border-primary hover:text-primary"
|
||||
onClick={() => {
|
||||
window.api.file
|
||||
.openPath(firstAccessiblePath)
|
||||
.catch((e) =>
|
||||
window.toast.error(
|
||||
formatErrorMessageWithPrefix(e, t('files.error.open_path', { path: firstAccessiblePath }))
|
||||
)
|
||||
)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
infoItems.push(<InfoTag key="permission-mode" text={permissionModeLabel} className="max-w-50" />)
|
||||
|
||||
if (infoItems.length === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
return <div className="ml-2 flex items-center gap-2">{infoItems}</div>
|
||||
}
|
||||
|
||||
export default ChatNavbarContent
|
||||
Loading…
Reference in New Issue
Block a user