refactor: Centralize emoji detection utility and improve avatar rendering

This commit is contained in:
kangfenmao 2025-03-01 23:16:12 +08:00
parent 92ab67eb3d
commit efa9c6c546
4 changed files with 32 additions and 34 deletions

View File

@ -4,15 +4,15 @@ import ImageStorage from '@renderer/services/ImageStorage'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime' import { setAvatar } from '@renderer/store/runtime'
import { setUserName } from '@renderer/store/settings' import { setUserName } from '@renderer/store/settings'
import { compressImage } from '@renderer/utils' import { compressImage, isEmoji } from '@renderer/utils'
import { Avatar , Input, Modal, Popover, Upload, Dropdown } from 'antd' import { Avatar, Dropdown, Input, Modal, Popover, Upload } from 'antd'
import { useState } from 'react' import { useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import EmojiPicker from '../EmojiPicker'
import { Center, HStack, VStack } from '../Layout' import { Center, HStack, VStack } from '../Layout'
import { TopView } from '../TopView' import { TopView } from '../TopView'
import EmojiPicker from '../EmojiPicker'
interface Props { interface Props {
resolve: (data: any) => void resolve: (data: any) => void
@ -51,12 +51,6 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
} }
} }
// modify the judgment function, more accurately detect Emoji
const isEmoji = (str: string) => {
// check if it is a string and is not base64 or URL format
return str && typeof str === 'string' && !str.startsWith('data:') && !str.startsWith('http');
}
const items = [ const items = [
{ {
key: 'upload', key: 'upload',
@ -88,11 +82,12 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
{ {
key: 'emoji', key: 'emoji',
label: ( label: (
<div onClick={(e) => { <div
e.stopPropagation() onClick={(e) => {
setEmojiPickerOpen(true) e.stopPropagation()
setDropdownOpen(false) setEmojiPickerOpen(true)
}}> setDropdownOpen(false)
}}>
{t('settings.general.emoji_picker')} {t('settings.general.emoji_picker')}
</div> </div>
) )
@ -111,8 +106,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
centered> centered>
<Center mt="30px"> <Center mt="30px">
<VStack alignItems="center" gap="10px"> <VStack alignItems="center" gap="10px">
<Dropdown <Dropdown
menu={{ items }} menu={{ items }}
trigger={['click']} trigger={['click']}
open={dropdownOpen} open={dropdownOpen}
align={{ offset: [0, 4] }} align={{ offset: [0, 4] }}
@ -134,11 +129,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
} }
}} }}
placement="bottom"> placement="bottom">
{isEmoji(avatar) ? ( {isEmoji(avatar) ? <EmojiAvatar>{avatar}</EmojiAvatar> : <UserAvatar src={avatar} />}
<EmojiAvatar>{avatar}</EmojiAvatar>
) : (
<UserAvatar src={avatar} />
)}
</Popover> </Popover>
</Dropdown> </Dropdown>
</VStack> </VStack>
@ -147,7 +138,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
<Input <Input
placeholder={t('settings.general.user_name.placeholder')} placeholder={t('settings.general.user_name.placeholder')}
value={userName} value={userName}
onChange={(e) => dispatch(setUserName(e.target.value))} onChange={(e) => dispatch(setUserName(e.target.value.trim()))}
style={{ flex: 1, textAlign: 'center', width: '100%' }} style={{ flex: 1, textAlign: 'center', width: '100%' }}
maxLength={30} maxLength={30}
/> />
@ -201,4 +192,4 @@ export default class UserPopup {
) )
}) })
} }
} }

View File

@ -12,6 +12,7 @@ import useAvatar from '@renderer/hooks/useAvatar'
import { useMinapps } from '@renderer/hooks/useMinapps' import { useMinapps } from '@renderer/hooks/useMinapps'
import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime' import { modelGenerating, useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { isEmoji } from '@renderer/utils'
import type { MenuProps } from 'antd' import type { MenuProps } from 'antd'
import { Tooltip } from 'antd' import { Tooltip } from 'antd'
import { Avatar } from 'antd' import { Avatar } from 'antd'
@ -57,10 +58,6 @@ const Sidebar: FC = () => {
}) })
} }
const isEmoji = (str: string) => {
return str && typeof str === 'string' && !str.startsWith('data:') && !str.startsWith('http');
}
return ( return (
<Container <Container
id="app-sidebar" id="app-sidebar"
@ -69,7 +66,7 @@ const Sidebar: FC = () => {
zIndex: minappShow ? 10000 : 'initial' zIndex: minappShow ? 10000 : 'initial'
}}> }}>
{isEmoji(avatar) ? ( {isEmoji(avatar) ? (
<EmojiAvatarSidebar onClick={onEditUser}>{avatar}</EmojiAvatarSidebar> <EmojiAvatar onClick={onEditUser}>{avatar}</EmojiAvatar>
) : ( ) : (
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} /> <AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
)} )}
@ -229,7 +226,7 @@ const AvatarImg = styled(Avatar)`
cursor: pointer; cursor: pointer;
` `
const EmojiAvatarSidebar = styled.div` const EmojiAvatar = styled.div`
width: 31px; width: 31px;
height: 31px; height: 31px;
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
@ -242,6 +239,7 @@ const EmojiAvatarSidebar = styled.div`
font-size: 16px; font-size: 16px;
cursor: pointer; cursor: pointer;
-webkit-app-region: none; -webkit-app-region: none;
border: 0.5px solid var(--color-border);
` `
const MainMenusContainer = styled.div` const MainMenusContainer = styled.div`

View File

@ -8,17 +8,13 @@ import { useMessageStyle, useSettings } from '@renderer/hooks/useSettings'
import { getMessageModelId } from '@renderer/services/MessagesService' import { getMessageModelId } from '@renderer/services/MessagesService'
import { getModelName } from '@renderer/services/ModelService' import { getModelName } from '@renderer/services/ModelService'
import { Assistant, Message, Model } from '@renderer/types' import { Assistant, Message, Model } from '@renderer/types'
import { firstLetter, removeLeadingEmoji } from '@renderer/utils' import { firstLetter, isEmoji, removeLeadingEmoji } from '@renderer/utils'
import { Avatar } from 'antd' import { Avatar } from 'antd'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { CSSProperties, FC, memo, useCallback, useMemo } from 'react' import { CSSProperties, FC, memo, useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
const isEmoji = (str: string) => {
return str && typeof str === 'string' && !str.startsWith('data:') && !str.startsWith('http');
}
interface Props { interface Props {
message: Message message: Message
assistant: Assistant assistant: Assistant

View File

@ -123,6 +123,19 @@ export function getLeadingEmoji(str: string): string {
return match ? match[0] : '' return match ? match[0] : ''
} }
export function isEmoji(str: string) {
if (str.startsWith('data:')) {
return false
}
if (str.startsWith('http')) {
return false
}
const emojiRegex = /^(\p{Emoji_Presentation}|\p{Emoji}\uFE0F)+/u
return str.match(emojiRegex)
}
export function isFreeModel(model: Model) { export function isFreeModel(model: Model) {
return (model.id + model.name).toLocaleLowerCase().includes('free') return (model.id + model.name).toLocaleLowerCase().includes('free')
} }