feat: messages styles optimization
This commit is contained in:
parent
731fb7860b
commit
8535edbdd1
@ -31,6 +31,7 @@
|
||||
--color-icon: #ffffff99;
|
||||
--color-icon-white: #ffffff;
|
||||
--color-border: #ffffff20;
|
||||
--color-error: #f44336;
|
||||
|
||||
--navbar-height: 42px;
|
||||
--sidebar-width: 55px;
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
user-select: text;
|
||||
margin-top: 4px;
|
||||
|
||||
p:first-of-type {
|
||||
margin-top: 0;
|
||||
|
||||
@ -23,7 +23,8 @@ const resources = {
|
||||
duplicate: 'Duplicate',
|
||||
copy: 'Copy',
|
||||
regenerate: 'Regenerate',
|
||||
provider: 'Provider'
|
||||
provider: 'Provider',
|
||||
you: 'You'
|
||||
},
|
||||
button: {
|
||||
add: 'Add',
|
||||
@ -168,7 +169,8 @@ const resources = {
|
||||
duplicate: '复制',
|
||||
copy: '复制',
|
||||
regenerate: '重新生成',
|
||||
provider: '提供商'
|
||||
provider: '提供商',
|
||||
you: '用户'
|
||||
},
|
||||
button: {
|
||||
add: '添加',
|
||||
|
||||
@ -170,7 +170,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
||||
{generating && (
|
||||
<Tooltip placement="top" title={t('assistant.input.pause')} arrow>
|
||||
<ToolbarButton type="text" onClick={onPause}>
|
||||
<PauseCircleOutlined />
|
||||
<PauseCircleOutlined style={{ color: 'var(--color-error)' }} />
|
||||
</ToolbarButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
@ -3,7 +3,7 @@ import { Avatar, Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import styled from 'styled-components'
|
||||
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 CodeBlock from './CodeBlock'
|
||||
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 { firstLetter } from '@renderer/utils'
|
||||
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 {
|
||||
message: Message
|
||||
@ -25,8 +28,11 @@ interface Props {
|
||||
const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) => {
|
||||
const avatar = useAvatar()
|
||||
const { t } = useTranslation()
|
||||
const generating = useAppSelector((state) => state.runtime.generating)
|
||||
const { assistant } = useAssistant(message.assistantId)
|
||||
|
||||
const isLastMessage = index === 0
|
||||
const isUserMessage = message.role === 'user'
|
||||
const canRegenerate = isLastMessage && message.role === 'assistant'
|
||||
|
||||
const onCopy = () => {
|
||||
@ -61,8 +67,23 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
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 (
|
||||
<MessageContainer key={message.id}>
|
||||
<MessageContainer key={message.id} style={{ borderBottom }}>
|
||||
<MessageHeader>
|
||||
<AvatarWrapper>
|
||||
{message.role === 'assistant' ? (
|
||||
<Avatar src={message.modelId ? getModelLogo(message.modelId) : Logo}>
|
||||
@ -71,7 +92,17 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
) : (
|
||||
<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>
|
||||
)}
|
||||
</MessageHeader>
|
||||
<MessageContent>
|
||||
{message.status === 'sending' && (
|
||||
<MessageContentLoading>
|
||||
@ -84,32 +115,31 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
</Markdown>
|
||||
)}
|
||||
{showMenu && (
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'}`}>
|
||||
<MenusBar className={`menubar ${isLastMessage && 'show'} ${message.content.length < 300 && 'user'}`}>
|
||||
{message.role === 'user' && (
|
||||
<Tooltip title="Edit" mouseEnterDelay={0.8}>
|
||||
<ActionButton>
|
||||
<EditOutlined onClick={onEdit} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('common.copy')} mouseEnterDelay={0.8}>
|
||||
<SwitcherOutlined onClick={onCopy} />
|
||||
<ActionButton>
|
||||
<CopyOutlined onClick={onCopy} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t('common.delete')} mouseEnterDelay={0.8}>
|
||||
<ActionButton>
|
||||
<DeleteOutlined onClick={onDelete} />
|
||||
</ActionButton>
|
||||
</Tooltip>
|
||||
{canRegenerate && (
|
||||
<Tooltip title={t('common.regenerate')} mouseEnterDelay={0.8}>
|
||||
<ActionButton>
|
||||
<SyncOutlined onClick={onRegenerate} />
|
||||
</ActionButton>
|
||||
</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>
|
||||
)}
|
||||
</MessageContent>
|
||||
@ -119,26 +149,21 @@ const MessageItem: FC<Props> = ({ message, index, showMenu, onDeleteMessage }) =
|
||||
|
||||
const MessageContainer = styled.div`
|
||||
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;
|
||||
justify-content: space-between;
|
||||
padding: 10px 18px;
|
||||
position: relative;
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
.menubar {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
&.show {
|
||||
opacity: 1;
|
||||
}
|
||||
&.user {
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.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`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@ -157,17 +223,9 @@ const MessageContentLoading = styled.div`
|
||||
const MenusBar = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
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`
|
||||
@ -176,4 +234,24 @@ const MessageMetadata = styled.div`
|
||||
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
|
||||
|
||||
@ -9,8 +9,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
import { NewButton } from '../HomePage'
|
||||
import { useShowAssistants } from '@renderer/hooks/useStore'
|
||||
import { capitalizeFirstLetter } from '@renderer/utils'
|
||||
import { isMac } from '@renderer/config/constant'
|
||||
import { upperFirst } from 'lodash'
|
||||
|
||||
interface Props {
|
||||
activeAssistant: Assistant
|
||||
@ -47,7 +47,7 @@ const NavigationCenter: FC<Props> = ({ activeAssistant }) => {
|
||||
<AssistantName>{assistant?.name}</AssistantName>
|
||||
<DropdownMenu menu={{ items, style: { maxHeight: '80vh', overflow: 'auto' } }} trigger={['click']}>
|
||||
<DropdownButton size="small" type="primary" ghost>
|
||||
{model ? capitalizeFirstLetter(model.name) : t('button.select_model')}
|
||||
{model ? upperFirst(model.name) : t('button.select_model')}
|
||||
</DropdownButton>
|
||||
</DropdownMenu>
|
||||
</NavbarCenter>
|
||||
|
||||
@ -199,12 +199,11 @@ export function estimateHistoryTokenCount(assistant: Assistant, msgs: Message[])
|
||||
return all.usedTokens - 7
|
||||
}
|
||||
|
||||
// 首字母大写
|
||||
export const capitalizeFirstLetter = (str: string) => {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1)
|
||||
}
|
||||
|
||||
// is valid proxy url
|
||||
/**
|
||||
* is valid proxy url
|
||||
* @param url proxy url
|
||||
* @returns boolean
|
||||
*/
|
||||
export const isValidProxyUrl = (url: string) => {
|
||||
return url.includes('://')
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user