mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
* refactor(ui): improve settings tab and assistant item UI - Remove SettingsTab from HomeTabs, open settings via navbar drawer instead - Add menu icon to assistant/agent items for quick access to settings popup - Remove SessionSettingsTab component (consolidated into settings popup) - Restore avatar display in bubble message style - Update topic/session item styles for consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(ui): simplify MessageHeader component logic - Removed unnecessary header visibility check in MessageHeader. - Updated justifyContent logic for UserWrap to account for multi-select mode. This change enhances the clarity and maintainability of the MessageHeader component. * refactor(ui): streamline ChatNavbar and SettingsTab components - Removed unused chat state from ChatNavbar. - Updated SettingsTab to conditionally render settings based on active topic or session. - Enhanced clarity and maintainability by reducing unnecessary checks and improving component logic. This change improves the overall user experience and code readability. * refactor(ui): enhance AgentItem and ChatNavbar components for improved UI - Updated AgentItem to conditionally hide the assistant icon based on settings. - Enhanced ChatNavbar to display the assistant's emoji and name with a new layout. - Introduced memoization for assistant name to optimize rendering. These changes improve the user interface and maintainability of the components. * refactor(ui): update HtmlArtifactsPopup to start in fullscreen mode - Changed initial state of isFullscreen in HtmlArtifactsPopup from false to true. This adjustment enhances the user experience by providing a more immersive view upon opening the popup. * refactor(types): remove 'settings' tab from Tab type - Updated the Tab type in chat.ts to remove the 'settings' option, simplifying the available tabs for the chat interface. This change streamlines the chat functionality and improves code clarity. * refactor(ui): enhance UserWrap styling in MessageHeader component - Added flex property to UserWrap to improve layout flexibility. This change enhances the responsiveness and layout management of the MessageHeader component. * refactor(ui): update HtmlArtifactsPopup to prevent drag on ViewControls - Added "nodrag" class to ViewControls to prevent drag events on double click. This change improves the user interaction by ensuring that double-clicking on the ViewControls does not trigger drag actions. * refactor(ui): adjust spacing in AgentLabel component - Updated the gap between items in the AgentLabel component from 1 to 2 for improved layout consistency. This change enhances the visual spacing and overall user interface of the AgentSettings page. * refactor(ui): remove unused useSessions hook from AgentItem component - Eliminated the useSessions hook from the AgentItem component to streamline the code and improve performance. This change enhances the maintainability of the AgentItem component by removing unnecessary dependencies. * refactor(ui): optimize MessageHeader component layout and logic - Introduced a memoized userNameJustifyContent calculation to streamline the justifyContent logic for UserWrap. - Simplified the HStack component by replacing inline logic with the new memoized value. These changes enhance the maintainability and clarity of the MessageHeader component. --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
186 lines
6.2 KiB
TypeScript
186 lines
6.2 KiB
TypeScript
import EmojiAvatar from '@renderer/components/Avatar/EmojiAvatar'
|
|
import { HStack } from '@renderer/components/Layout'
|
|
import UserPopup from '@renderer/components/Popups/UserPopup'
|
|
import { APP_NAME, AppLogo, isLocalAi } from '@renderer/config/env'
|
|
import { getModelLogoById } from '@renderer/config/models'
|
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
|
import { useAgent } from '@renderer/hooks/agents/useAgent'
|
|
import useAvatar from '@renderer/hooks/useAvatar'
|
|
import { useChatContext } from '@renderer/hooks/useChatContext'
|
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
|
import { useRuntime } from '@renderer/hooks/useRuntime'
|
|
import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
|
|
import { getMessageModelId } from '@renderer/services/MessagesService'
|
|
import { getModelName } from '@renderer/services/ModelService'
|
|
import type { Assistant, Model, Topic } from '@renderer/types'
|
|
import type { Message } from '@renderer/types/newMessage'
|
|
import { firstLetter, isEmoji, removeLeadingEmoji } from '@renderer/utils'
|
|
import { Avatar, Checkbox, Tooltip } from 'antd'
|
|
import dayjs from 'dayjs'
|
|
import { Sparkle } from 'lucide-react'
|
|
import type { FC } from 'react'
|
|
import { memo, useCallback, useMemo } from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import styled from 'styled-components'
|
|
|
|
interface Props {
|
|
message: Message
|
|
assistant: Assistant
|
|
model?: Model
|
|
topic: Topic
|
|
isGroupContextMessage?: boolean
|
|
}
|
|
|
|
const getAvatarSource = (isLocalAi: boolean, modelId: string | undefined) => {
|
|
if (isLocalAi) return AppLogo
|
|
return modelId ? getModelLogoById(modelId) : undefined
|
|
}
|
|
|
|
const MessageHeader: FC<Props> = memo(({ assistant, model, message, topic, isGroupContextMessage }) => {
|
|
const avatar = useAvatar()
|
|
const { theme } = useTheme()
|
|
const { userName, sidebarIcons } = useSettings()
|
|
const { chat } = useRuntime()
|
|
const { activeTopicOrSession, activeAgentId } = chat
|
|
const { agent } = useAgent(activeAgentId)
|
|
const isAgentView = activeTopicOrSession === 'session'
|
|
const { t } = useTranslation()
|
|
const { isBubbleStyle } = useMessageStyle()
|
|
const { openMinappById } = useMinappPopup()
|
|
|
|
const { isMultiSelectMode, selectedMessageIds, handleSelectMessage } = useChatContext(topic)
|
|
|
|
const isSelected = selectedMessageIds?.includes(message.id)
|
|
|
|
const avatarSource = useMemo(() => getAvatarSource(isLocalAi, getMessageModelId(message)), [message])
|
|
|
|
const getUserName = useCallback(() => {
|
|
if (isLocalAi && message.role !== 'user') {
|
|
return APP_NAME
|
|
}
|
|
|
|
if (isAgentView && message.role === 'assistant') {
|
|
return agent?.name ?? t('common.unknown')
|
|
}
|
|
|
|
if (message.role === 'assistant') {
|
|
return getModelName(model) || getMessageModelId(message) || ''
|
|
}
|
|
|
|
return userName || t('common.you')
|
|
}, [agent?.name, isAgentView, message, model, t, userName])
|
|
|
|
const isAssistantMessage = message.role === 'assistant'
|
|
const isUserMessage = message.role === 'user'
|
|
const showMinappIcon = sidebarIcons.visible.includes('minapp')
|
|
|
|
const avatarName = useMemo(() => firstLetter(assistant?.name).toUpperCase(), [assistant?.name])
|
|
const username = useMemo(() => removeLeadingEmoji(getUserName()), [getUserName])
|
|
|
|
const showMiniApp = useCallback(() => {
|
|
showMinappIcon && model?.provider && openMinappById(model.provider)
|
|
// because don't need openMinappById to be a dependency
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [model?.provider, showMinappIcon])
|
|
|
|
const userNameJustifyContent = useMemo(() => {
|
|
if (!isBubbleStyle) return 'flex-start'
|
|
if (isUserMessage && !isMultiSelectMode) return 'flex-end'
|
|
return 'flex-start'
|
|
}, [isBubbleStyle, isUserMessage, isMultiSelectMode])
|
|
|
|
return (
|
|
<Container className="message-header">
|
|
{isAssistantMessage ? (
|
|
<Avatar
|
|
src={avatarSource}
|
|
size={35}
|
|
style={{
|
|
borderRadius: '25%',
|
|
cursor: showMinappIcon ? 'pointer' : 'default',
|
|
border: isLocalAi ? '1px solid var(--color-border-soft)' : 'none',
|
|
filter: theme === 'dark' ? 'invert(0.05)' : undefined
|
|
}}
|
|
onClick={showMiniApp}>
|
|
{avatarName}
|
|
</Avatar>
|
|
) : (
|
|
<>
|
|
{isEmoji(avatar) ? (
|
|
<EmojiAvatar onClick={() => UserPopup.show()} size={35} fontSize={20}>
|
|
{avatar}
|
|
</EmojiAvatar>
|
|
) : (
|
|
<Avatar
|
|
src={avatar}
|
|
size={35}
|
|
style={{ borderRadius: '25%', cursor: 'pointer' }}
|
|
onClick={() => UserPopup.show()}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
<UserWrap>
|
|
<HStack alignItems="center" justifyContent={userNameJustifyContent}>
|
|
<UserName isBubbleStyle={isBubbleStyle} theme={theme}>
|
|
{username}
|
|
</UserName>
|
|
{isGroupContextMessage && (
|
|
<Tooltip title={t('chat.message.useful.tip')}>
|
|
<Sparkle fill="var(--color-primary)" strokeWidth={0} size={18} />
|
|
</Tooltip>
|
|
)}
|
|
</HStack>
|
|
<InfoWrap className="message-header-info-wrap">
|
|
<MessageTime>{dayjs(message?.updatedAt ?? message.createdAt).format('MM/DD HH:mm')}</MessageTime>
|
|
</InfoWrap>
|
|
</UserWrap>
|
|
{isMultiSelectMode && (
|
|
<Checkbox
|
|
checked={isSelected}
|
|
onChange={(e) => handleSelectMessage(message.id, e.target.checked)}
|
|
style={{ position: 'absolute', right: 0, top: 0 }}
|
|
/>
|
|
)}
|
|
</Container>
|
|
)
|
|
})
|
|
|
|
MessageHeader.displayName = 'MessageHeader'
|
|
|
|
const Container = styled.div`
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 10px;
|
|
position: relative;
|
|
margin-bottom: 10px;
|
|
`
|
|
|
|
const UserWrap = styled.div`
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
flex: 1;
|
|
`
|
|
|
|
const InfoWrap = styled.div`
|
|
display: flex;
|
|
flex-direction: row;
|
|
align-items: center;
|
|
gap: 4px;
|
|
`
|
|
|
|
const UserName = styled.span<{ isBubbleStyle?: boolean; theme?: string }>`
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: ${(props) => (props.isBubbleStyle && props.theme === 'dark' ? 'white' : 'var(--color-text)')};
|
|
`
|
|
|
|
const MessageTime = styled.div`
|
|
font-size: 10px;
|
|
color: var(--color-text-3);
|
|
`
|
|
|
|
export default MessageHeader
|