mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-22 17:00:14 +08:00
166 lines
5.6 KiB
TypeScript
166 lines
5.6 KiB
TypeScript
import HorizontalScrollContainer from '@renderer/components/HorizontalScrollContainer'
|
|
import { useActiveAgent } from '@renderer/hooks/agents/useActiveAgent'
|
|
import { useActiveSession } from '@renderer/hooks/agents/useActiveSession'
|
|
import { useUpdateSession } from '@renderer/hooks/agents/useUpdateSession'
|
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
|
import type { AgentEntity, AgentSessionEntity, ApiModel, Assistant } from '@renderer/types'
|
|
import { formatErrorMessageWithPrefix } from '@renderer/utils/error'
|
|
import { t } from 'i18next'
|
|
import { ChevronRight, Folder } from 'lucide-react'
|
|
import type { FC, ReactNode } from 'react'
|
|
import { useCallback } from 'react'
|
|
import { twMerge } from 'tailwind-merge'
|
|
|
|
import { AgentSettingsPopup, SessionSettingsPopup } from '../../settings/AgentSettings'
|
|
import { AgentLabel, SessionLabel } from '../../settings/AgentSettings/shared'
|
|
import SelectAgentBaseModelButton from './SelectAgentBaseModelButton'
|
|
import SelectModelButton from './SelectModelButton'
|
|
|
|
const cn = (...inputs: any[]) => twMerge(inputs)
|
|
|
|
interface Props {
|
|
assistant: Assistant
|
|
}
|
|
|
|
const ChatNavbarContent: FC<Props> = ({ assistant }) => {
|
|
const { chat } = useRuntime()
|
|
const { activeTopicOrSession } = chat
|
|
const { agent: activeAgent } = useActiveAgent()
|
|
const { session: activeSession } = useActiveSession()
|
|
const { updateModel } = useUpdateSession(activeAgent?.id ?? null)
|
|
|
|
const handleUpdateModel = useCallback(
|
|
async (model: ApiModel) => {
|
|
if (!activeAgent || !activeSession) return
|
|
return updateModel(activeSession.id, model.id, { showSuccessToast: false })
|
|
},
|
|
[activeAgent, activeSession, updateModel]
|
|
)
|
|
|
|
return (
|
|
<>
|
|
{activeTopicOrSession === 'topic' && <SelectModelButton assistant={assistant} />}
|
|
{activeTopicOrSession === 'session' && activeAgent && (
|
|
<HorizontalScrollContainer className="ml-2 flex-initial">
|
|
<div className="flex flex-nowrap items-center gap-2">
|
|
{/* Agent Label */}
|
|
<div
|
|
className="flex h-full cursor-pointer items-center"
|
|
onClick={() => AgentSettingsPopup.show({ agentId: activeAgent.id })}>
|
|
<AgentLabel
|
|
agent={activeAgent}
|
|
classNames={{ name: 'max-w-40 text-xs', avatar: 'h-4.5 w-4.5', container: 'gap-1.5' }}
|
|
/>
|
|
</div>
|
|
|
|
{activeSession && (
|
|
<>
|
|
{/* Separator */}
|
|
<ChevronRight className="h-4 w-4 text-gray-400" />
|
|
|
|
{/* Session Label */}
|
|
<div
|
|
className="flex h-full cursor-pointer items-center"
|
|
onClick={() =>
|
|
SessionSettingsPopup.show({
|
|
agentId: activeAgent.id,
|
|
sessionId: activeSession.id
|
|
})
|
|
}>
|
|
<SessionLabel session={activeSession} className="max-w-40 text-xs" />
|
|
</div>
|
|
|
|
{/* Separator */}
|
|
<ChevronRight className="h-4 w-4 text-gray-400" />
|
|
|
|
{/* Model Button */}
|
|
<SelectAgentBaseModelButton
|
|
agentBase={activeSession}
|
|
onSelect={async (model) => {
|
|
await handleUpdateModel(model)
|
|
}}
|
|
/>
|
|
|
|
{/* Separator */}
|
|
<ChevronRight className="h-4 w-4 text-gray-400" />
|
|
|
|
{/* Workspace Meta */}
|
|
<SessionWorkspaceMeta agent={activeAgent} session={activeSession} />
|
|
</>
|
|
)}
|
|
</div>
|
|
</HorizontalScrollContainer>
|
|
)}
|
|
</>
|
|
)
|
|
}
|
|
|
|
const SessionWorkspaceMeta: FC<{ agent: AgentEntity; session: AgentSessionEntity }> = ({ agent, session }) => {
|
|
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(
|
|
'flex items-center gap-1.5 text-foreground-500 text-xs dark:text-foreground-400',
|
|
onClick !== undefined ? 'cursor-pointer' : undefined,
|
|
className
|
|
)}
|
|
title={text}
|
|
onClick={onClick}>
|
|
<Folder className="h-3.5 w-3.5 shrink-0" />
|
|
<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
|