feat: messages styles optimization

This commit is contained in:
kangfenmao 2024-07-23 14:51:55 +08:00
parent 731fb7860b
commit 8535edbdd1
7 changed files with 139 additions and 60 deletions

View File

@ -31,6 +31,7 @@
--color-icon: #ffffff99; --color-icon: #ffffff99;
--color-icon-white: #ffffff; --color-icon-white: #ffffff;
--color-border: #ffffff20; --color-border: #ffffff20;
--color-error: #f44336;
--navbar-height: 42px; --navbar-height: 42px;
--sidebar-width: 55px; --sidebar-width: 55px;

View File

@ -3,7 +3,6 @@
font-size: 15px; font-size: 15px;
line-height: 1.6; line-height: 1.6;
user-select: text; user-select: text;
margin-top: 4px;
p:first-of-type { p:first-of-type {
margin-top: 0; margin-top: 0;

View File

@ -23,7 +23,8 @@ const resources = {
duplicate: 'Duplicate', duplicate: 'Duplicate',
copy: 'Copy', copy: 'Copy',
regenerate: 'Regenerate', regenerate: 'Regenerate',
provider: 'Provider' provider: 'Provider',
you: 'You'
}, },
button: { button: {
add: 'Add', add: 'Add',
@ -168,7 +169,8 @@ const resources = {
duplicate: '复制', duplicate: '复制',
copy: '复制', copy: '复制',
regenerate: '重新生成', regenerate: '重新生成',
provider: '提供商' provider: '提供商',
you: '用户'
}, },
button: { button: {
add: '添加', add: '添加',

View File

@ -170,7 +170,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
{generating && ( {generating && (
<Tooltip placement="top" title={t('assistant.input.pause')} arrow> <Tooltip placement="top" title={t('assistant.input.pause')} arrow>
<ToolbarButton type="text" onClick={onPause}> <ToolbarButton type="text" onClick={onPause}>
<PauseCircleOutlined /> <PauseCircleOutlined style={{ color: 'var(--color-error)' }} />
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
)} )}

View File

@ -3,7 +3,7 @@ import { Avatar, Tooltip } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import useAvatar from '@renderer/hooks/useAvatar' import useAvatar from '@renderer/hooks/useAvatar'
import { DeleteOutlined, EditOutlined, SwitcherOutlined } from '@ant-design/icons' import { CopyOutlined, DeleteOutlined, EditOutlined } from '@ant-design/icons'
import Markdown from 'react-markdown' import Markdown from 'react-markdown'
import CodeBlock from './CodeBlock' import CodeBlock from './CodeBlock'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
@ -12,7 +12,10 @@ import Logo from '@renderer/assets/images/logo.png'
import { SyncOutlined } from '@ant-design/icons' import { SyncOutlined } from '@ant-design/icons'
import { firstLetter } from '@renderer/utils' import { firstLetter } from '@renderer/utils'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { isEmpty } from 'lodash' import { isEmpty, upperFirst } from 'lodash'
import dayjs from 'dayjs'
import { useAppSelector } from '@renderer/store'
import { useAssistant } from '@renderer/hooks/useAssistant'
interface Props { interface Props {
message: Message message: Message
@ -25,8 +28,11 @@ interface Props {
const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) => { const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) => {
const avatar = useAvatar() const avatar = useAvatar()
const { t } = useTranslation() const { t } = useTranslation()
const generating = useAppSelector((state) => state.runtime.generating)
const { assistant } = useAssistant(message.assistantId)
const isLastMessage = index === 0 const isLastMessage = index === 0
const isUserMessage = message.role === 'user'
const canRegenerate = isLastMessage && message.role === 'assistant' const canRegenerate = isLastMessage && message.role === 'assistant'
const onCopy = () => { const onCopy = () => {
@ -61,17 +67,42 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
return message.content return message.content
} }
const getUserName = () => {
if (message.id === 'assistant') {
return assistant.name
}
if (message.role === 'assistant') {
return upperFirst(message.modelId)
}
return t('common.you')
}
const borderBottom = (isLastMessage && !isUserMessage) || generating ? 'none' : undefined
return ( return (
<MessageContainer key={message.id}> <MessageContainer key={message.id} style={{ borderBottom }}>
<AvatarWrapper> <MessageHeader>
{message.role === 'assistant' ? ( <AvatarWrapper>
<Avatar src={message.modelId ? getModelLogo(message.modelId) : Logo}> {message.role === 'assistant' ? (
{firstLetter(message.modelId).toUpperCase()} <Avatar src={message.modelId ? getModelLogo(message.modelId) : Logo}>
</Avatar> {firstLetter(message.modelId).toUpperCase()}
) : ( </Avatar>
<Avatar src={avatar} /> ) : (
<Avatar src={avatar} />
)}
<UserWrap>
<UserName>{getUserName()}</UserName>
<MessageTime>{dayjs(message.createdAt).format('MM/DD HH:mm')}</MessageTime>
</UserWrap>
</AvatarWrapper>
{message.usage && (
<MessageMetadata>
Tokens: {message.usage.total_tokens} | {message.usage.prompt_tokens}{message.usage.completion_tokens}
</MessageMetadata>
)} )}
</AvatarWrapper> </MessageHeader>
<MessageContent> <MessageContent>
{message.status === 'sending' && ( {message.status === 'sending' && (
<MessageContentLoading> <MessageContentLoading>
@ -84,32 +115,31 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
</Markdown> </Markdown>
)} )}
{showMenu && ( {showMenu && (
<MenusBar className={`menubar ${isLastMessage && 'show'}`}> <MenusBar className={`menubar ${isLastMessage && 'show'} ${message.content.length < 300 && 'user'}`}>
{message.role === 'user' && ( {message.role === 'user' && (
<Tooltip title="Edit" mouseEnterDelay={0.8}> <Tooltip title="Edit" mouseEnterDelay={0.8}>
<EditOutlined onClick={onEdit} /> <ActionButton>
<EditOutlined onClick={onEdit} />
</ActionButton>
</Tooltip> </Tooltip>
)} )}
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}> <Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
<SwitcherOutlined onClick={onCopy} /> <ActionButton>
<CopyOutlined onClick={onCopy} />
</ActionButton>
</Tooltip> </Tooltip>
<Tooltip title={t('common.delete')} mouseEnterDelay={0.8}> <Tooltip title={t('common.delete')} mouseEnterDelay={0.8}>
<DeleteOutlined onClick={onDelete} /> <ActionButton>
<DeleteOutlined onClick={onDelete} />
</ActionButton>
</Tooltip> </Tooltip>
{canRegenerate && ( {canRegenerate && (
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}> <Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
<SyncOutlined onClick={onRegenerate} /> <ActionButton>
<SyncOutlined onClick={onRegenerate} />
</ActionButton>
</Tooltip> </Tooltip>
)} )}
<MessageMetadata>{message.modelId}</MessageMetadata>
{message.usage && (
<>
<MessageMetadata>
tokens: {message.usage.total_tokens} (in:{message.usage.prompt_tokens}/out:
{message.usage.completion_tokens})
</MessageMetadata>
</>
)}
</MenusBar> </MenusBar>
)} )}
</MessageContent> </MessageContent>
@ -119,26 +149,21 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
const MessageContainer = styled.div` const MessageContainer = styled.div`
display: flex; display: flex;
flex-direction: row;
padding: 10px 15px;
position: relative;
`
const AvatarWrapper = styled.div`
margin-right: 10px;
`
const MessageContent = styled.div`
display: flex;
flex: 1;
flex-direction: column; flex-direction: column;
justify-content: space-between; padding: 10px 18px;
position: relative;
border-bottom: 0.5px solid var(--color-border);
.menubar { .menubar {
opacity: 0; opacity: 0;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
&.show { &.show {
opacity: 1; opacity: 1;
} }
&.user {
position: absolute;
top: 15px;
right: 10px;
}
} }
&:hover { &:hover {
.menubar { .menubar {
@ -147,6 +172,47 @@ const MessageContent = styled.div`
} }
` `
const MessageHeader = styled.div`
margin-right: 10px;
margin-bottom: 10px;
display: flex;
flex-direction: row;
align-items: center;
padding-bottom: 4px;
justify-content: space-between;
`
const AvatarWrapper = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const UserWrap = styled.div`
display: flex;
flex-direction: row;
align-items: center;
`
const MessageTime = styled.div`
font-size: 12px;
color: var(--color-text-2);
margin-left: 12px;
`
const UserName = styled.div`
font-size: 14px;
font-weight: 600;
margin-left: 12px;
`
const MessageContent = styled.div`
display: flex;
flex: 1;
flex-direction: column;
justify-content: space-between;
`
const MessageContentLoading = styled.div` const MessageContentLoading = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -157,17 +223,9 @@ const MessageContentLoading = styled.div`
const MenusBar = styled.div` const MenusBar = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: flex-start; justify-content: flex-end;
align-items: center;
gap: 6px; gap: 6px;
.anticon {
cursor: pointer;
margin-right: 8px;
font-size: 15px;
color: var(--color-icon);
&:hover {
color: var(--color-text-1);
}
}
` `
const MessageMetadata = styled.div` const MessageMetadata = styled.div`
@ -176,4 +234,24 @@ const MessageMetadata = styled.div`
user-select: text; user-select: text;
` `
const ActionButton = styled.div`
cursor: pointer;
border: 1px solid var(--color-border);
border-radius: 8px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 30px;
height: 30px;
.anticon {
cursor: pointer;
font-size: 14px;
color: var(--color-icon);
}
&:hover {
color: var(--color-text-1);
}
`
export default MessageItem export default MessageItem

View File

@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import { NewButton } from '../HomePage' import { NewButton } from '../HomePage'
import { useShowAssistants } from '@renderer/hooks/useStore' import { useShowAssistants } from '@renderer/hooks/useStore'
import { capitalizeFirstLetter } from '@renderer/utils'
import { isMac } from '@renderer/config/constant' import { isMac } from '@renderer/config/constant'
import { upperFirst } from 'lodash'
interface Props { interface Props {
activeAssistant: Assistant activeAssistant: Assistant
@ -47,7 +47,7 @@ const NavigationCenter: FC<Props> = ({ activeAssistant }) => {
<AssistantName>{assistant?.name}</AssistantName> <AssistantName>{assistant?.name}</AssistantName>
<DropdownMenu menu={{ items, style: { maxHeight: '80vh', overflow: 'auto' } }} trigger={['click']}> <DropdownMenu menu={{ items, style: { maxHeight: '80vh', overflow: 'auto' } }} trigger={['click']}>
<DropdownButton size="small" type="primary" ghost> <DropdownButton size="small" type="primary" ghost>
{model ? capitalizeFirstLetter(model.name) : t('button.select_model')} {model ? upperFirst(model.name) : t('button.select_model')}
</DropdownButton> </DropdownButton>
</DropdownMenu> </DropdownMenu>
</NavbarCenter> </NavbarCenter>

View File

@ -199,12 +199,11 @@ export function estimateHistoryTokenCount(assistant: Assistant, msgs: Message[])
return all.usedTokens - 7 return all.usedTokens - 7
} }
// 首字母大写 /**
export const capitalizeFirstLetter = (str: string) => { * is valid proxy url
return str.charAt(0).toUpperCase() + str.slice(1) * @param url proxy url
} * @returns boolean
*/
// is valid proxy url
export const isValidProxyUrl = (url: string) => { export const isValidProxyUrl = (url: string) => {
return url.includes('://') return url.includes('://')
} }