fix: assistant and agent emoji

This commit is contained in:
kangfenmao 2025-02-25 19:53:04 +08:00
parent a047048f69
commit f448d8a8db
12 changed files with 133 additions and 60 deletions

View File

@ -79,7 +79,7 @@
"assistant.search.placeholder": "Search", "assistant.search.placeholder": "Search",
"deeply_thought": "Deeply thought ({{secounds}} seconds)", "deeply_thought": "Deeply thought ({{secounds}} seconds)",
"default.description": "Hello, I'm Default Assistant. You can start chatting with me right away", "default.description": "Hello, I'm Default Assistant. You can start chatting with me right away",
"default.name": "⭐️ Default Assistant", "default.name": "Default Assistant",
"default.topic.name": "Default Topic", "default.topic.name": "Default Topic",
"input.clear": "Clear {{Command}}", "input.clear": "Clear {{Command}}",
"input.clear.content": "Do you want to clear all messages of the current topic?", "input.clear.content": "Do you want to clear all messages of the current topic?",

View File

@ -79,7 +79,7 @@
"assistant.search.placeholder": "検索", "assistant.search.placeholder": "検索",
"deeply_thought": "深く考えています({{secounds}} 秒)", "deeply_thought": "深く考えています({{secounds}} 秒)",
"default.description": "こんにちは、私はデフォルトのアシスタントです。すぐにチャットを始められます。", "default.description": "こんにちは、私はデフォルトのアシスタントです。すぐにチャットを始められます。",
"default.name": "⭐️ デフォルトアシスタント", "default.name": "デフォルトアシスタント",
"default.topic.name": "デフォルトトピック", "default.topic.name": "デフォルトトピック",
"input.clear": "クリア {{Command}}", "input.clear": "クリア {{Command}}",
"input.clear.content": "現在のトピックのすべてのメッセージをクリアしますか?", "input.clear.content": "現在のトピックのすべてのメッセージをクリアしますか?",
@ -866,4 +866,4 @@
"visualization": "可視化" "visualization": "可視化"
} }
} }
} }

View File

@ -79,7 +79,7 @@
"assistant.search.placeholder": "Поиск", "assistant.search.placeholder": "Поиск",
"deeply_thought": "Мыслим ({{secounds}} секунд)", "deeply_thought": "Мыслим ({{secounds}} секунд)",
"default.description": "Привет, я Ассистент по умолчанию. Вы можете начать общаться со мной прямо сейчас", "default.description": "Привет, я Ассистент по умолчанию. Вы можете начать общаться со мной прямо сейчас",
"default.name": "⭐️ Ассистент по умолчанию", "default.name": "Ассистент по умолчанию",
"default.topic.name": "Топик по умолчанию", "default.topic.name": "Топик по умолчанию",
"input.clear": "Очистить {{Command}}", "input.clear": "Очистить {{Command}}",
"input.clear.content": "Хотите очистить все сообщения текущего топика?", "input.clear.content": "Хотите очистить все сообщения текущего топика?",

View File

@ -79,7 +79,7 @@
"assistant.search.placeholder": "搜索", "assistant.search.placeholder": "搜索",
"deeply_thought": "已深度思考(用时 {{secounds}} 秒)", "deeply_thought": "已深度思考(用时 {{secounds}} 秒)",
"default.description": "你好,我是默认助手。你可以立刻开始跟我聊天。", "default.description": "你好,我是默认助手。你可以立刻开始跟我聊天。",
"default.name": "⭐️ 默认助手", "default.name": "默认助手",
"default.topic.name": "默认话题", "default.topic.name": "默认话题",
"input.clear": "清空消息 {{Command}}", "input.clear": "清空消息 {{Command}}",
"input.clear.content": "确定要清除当前会话所有消息吗?", "input.clear.content": "确定要清除当前会话所有消息吗?",

View File

@ -79,7 +79,7 @@
"assistant.search.placeholder": "搜尋", "assistant.search.placeholder": "搜尋",
"deeply_thought": "已深度思考(用時 {{secounds}} 秒)", "deeply_thought": "已深度思考(用時 {{secounds}} 秒)",
"default.description": "你好,我是預設助手。你可以立即開始與我聊天。", "default.description": "你好,我是預設助手。你可以立即開始與我聊天。",
"default.name": "⭐️ 預設助手", "default.name": "預設助手",
"default.topic.name": "預設話題", "default.topic.name": "預設話題",
"input.clear": "清除 {{Command}}", "input.clear": "清除 {{Command}}",
"input.clear.content": "您想要清除當前話題的所有訊息嗎?", "input.clear.content": "您想要清除當前話題的所有訊息嗎?",

View File

@ -66,14 +66,10 @@ const AgentsPage: FC = () => {
return { 搜索结果: Array.from(uniqueAgents.values()) } return { 搜索结果: Array.from(uniqueAgents.values()) }
}, [agentGroups, search]) }, [agentGroups, search])
const getAgentName = (agent: Agent) => {
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
}
const onAddAgentConfirm = useCallback( const onAddAgentConfirm = useCallback(
(agent: Agent) => { (agent: Agent) => {
window.modal.confirm({ window.modal.confirm({
title: getAgentName(agent), title: agent.name,
content: ( content: (
<AgentPrompt> <AgentPrompt>
<ReactMarkdown className="markdown">{agent.description || agent.prompt}</ReactMarkdown> <ReactMarkdown className="markdown">{agent.description || agent.prompt}</ReactMarkdown>

View File

@ -107,10 +107,14 @@ const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch,
onSwitch(assistant) onSwitch(assistant)
}, [clickAssistantToShowTopic, onSwitch, assistant, topicPosition]) }, [clickAssistantToShowTopic, onSwitch, assistant, topicPosition])
const assistantName = assistant.name || t('chat.default.name')
return ( return (
<Dropdown menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}> <Dropdown menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
<Container onClick={handleSwitch} className={isActive ? 'active' : ''}> <Container onClick={handleSwitch} className={isActive ? 'active' : ''}>
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName> <AssistantName className="name">
{assistant.emoji ? `${assistant.emoji} ${assistantName}` : assistantName}
</AssistantName>
{isActive && ( {isActive && (
<MenuButton onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}> <MenuButton onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
<TopicCount className="topics-count">{assistant.topics.length}</TopicCount> <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>

View File

@ -25,13 +25,13 @@ const AssistantPromptSettings: React.FC<Props> = ({ assistant, updateAssistant,
const { t } = useTranslation() const { t } = useTranslation()
const onUpdate = () => { const onUpdate = () => {
const _assistant = { ...assistant, name: `${emoji} ${name}`.trim(), prompt } const _assistant = { ...assistant, name: name.trim(), emoji, prompt }
updateAssistant(_assistant) updateAssistant(_assistant)
} }
const handleEmojiSelect = (selectedEmoji: string) => { const handleEmojiSelect = (selectedEmoji: string) => {
setEmoji(selectedEmoji) setEmoji(selectedEmoji)
const _assistant = { ...assistant, name: `${selectedEmoji} ${name}`.trim(), prompt } const _assistant = { ...assistant, name: name.trim(), emoji: selectedEmoji, prompt }
updateAssistant(_assistant) updateAssistant(_assistant)
} }

View File

@ -1,11 +1,13 @@
import { QuestionCircleOutlined } from '@ant-design/icons' import { CloseCircleFilled, QuestionCircleOutlined } from '@ant-design/icons'
import EmojiPicker from '@renderer/components/EmojiPicker'
import { HStack } from '@renderer/components/Layout' import { HStack } from '@renderer/components/Layout'
import { TopView } from '@renderer/components/TopView' import { TopView } from '@renderer/components/TopView'
import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useDefaultAssistant } from '@renderer/hooks/useAssistant' import { useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { AssistantSettings as AssistantSettingsType } from '@renderer/types' import { AssistantSettings as AssistantSettingsType } from '@renderer/types'
import { Button, Col, Input, InputNumber, Modal, Row, Slider, Switch, Tooltip } from 'antd' import { getLeadingEmoji, modalConfirm } from '@renderer/utils'
import { Button, Col, Input, InputNumber, Modal, Popover, Row, Slider, Switch, Tooltip } from 'antd'
import TextArea from 'antd/es/input/TextArea' import TextArea from 'antd/es/input/TextArea'
import { Dispatch, FC, SetStateAction, useState } from 'react' import { Dispatch, FC, SetStateAction, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@ -20,6 +22,10 @@ const AssistantSettings: FC = () => {
const [enableMaxTokens, setEnableMaxTokens] = useState(defaultAssistant?.settings?.enableMaxTokens ?? false) const [enableMaxTokens, setEnableMaxTokens] = useState(defaultAssistant?.settings?.enableMaxTokens ?? false)
const [maxTokens, setMaxTokens] = useState(defaultAssistant?.settings?.maxTokens ?? 0) const [maxTokens, setMaxTokens] = useState(defaultAssistant?.settings?.maxTokens ?? 0)
const [topP, setTopP] = useState(defaultAssistant.settings?.topP ?? 1) const [topP, setTopP] = useState(defaultAssistant.settings?.topP ?? 1)
const [emoji, setEmoji] = useState(defaultAssistant.emoji || getLeadingEmoji(defaultAssistant.name) || '')
const [name, setName] = useState(
defaultAssistant.name.replace(getLeadingEmoji(defaultAssistant.name) || '', '').trim()
)
const { theme } = useTheme() const { theme } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
@ -73,15 +79,56 @@ const AssistantSettings: FC = () => {
}) })
} }
const handleEmojiSelect = (selectedEmoji: string) => {
setEmoji(selectedEmoji)
updateDefaultAssistant({ ...defaultAssistant, emoji: selectedEmoji, name })
}
const handleEmojiDelete = () => {
setEmoji('')
updateDefaultAssistant({ ...defaultAssistant, emoji: '', name })
}
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newName = e.target.value
setName(newName)
updateDefaultAssistant({ ...defaultAssistant, name: newName })
}
return ( return (
<SettingContainer style={{ height: 'auto', background: 'transparent', padding: 0 }} theme={theme}> <SettingContainer style={{ height: 'auto', background: 'transparent', padding: 0 }} theme={theme}>
<SettingSubtitle style={{ marginTop: 0 }}>{t('common.name')}</SettingSubtitle> <SettingSubtitle style={{ marginTop: 0 }}>{t('common.name')}</SettingSubtitle>
<Input <HStack gap={8} alignItems="center" style={{ margin: '10px 0' }}>
placeholder={t('common.assistant') + t('common.name')} <Popover content={<EmojiPicker onEmojiClick={handleEmojiSelect} />} arrow>
value={defaultAssistant.name} <EmojiButtonWrapper>
onChange={(e) => updateDefaultAssistant({ ...defaultAssistant, name: e.target.value })} <Button style={{ fontSize: 20, padding: '4px', minWidth: '32px', height: '32px' }}>{emoji}</Button>
style={{ margin: '10px 0' }} {emoji && (
/> <CloseCircleFilled
className="delete-icon"
onClick={(e) => {
e.stopPropagation()
handleEmojiDelete()
}}
style={{
display: 'none',
position: 'absolute',
top: '-8px',
right: '-8px',
fontSize: '16px',
color: '#ff4d4f',
cursor: 'pointer'
}}
/>
)}
</EmojiButtonWrapper>
</Popover>
<Input
placeholder={t('common.assistant') + t('common.name')}
value={name}
onChange={handleNameChange}
style={{ flex: 1 }}
/>
</HStack>
<SettingSubtitle>{t('common.prompt')}</SettingSubtitle> <SettingSubtitle>{t('common.prompt')}</SettingSubtitle>
<TextArea <TextArea
rows={4} rows={4}
@ -182,7 +229,7 @@ const AssistantSettings: FC = () => {
/> />
</Col> </Col>
</Row> </Row>
<Row align="middle"> <Row align="middle" style={{ marginBottom: 10 }}>
<HStack alignItems="center"> <HStack alignItems="center">
<Label>{t('chat.settings.max_tokens')}</Label> <Label>{t('chat.settings.max_tokens')}</Label>
<Tooltip title={t('chat.settings.max_tokens.tip')}> <Tooltip title={t('chat.settings.max_tokens.tip')}>
@ -192,41 +239,39 @@ const AssistantSettings: FC = () => {
<Switch <Switch
style={{ marginLeft: 10 }} style={{ marginLeft: 10 }}
checked={enableMaxTokens} checked={enableMaxTokens}
onChange={(enabled) => { onChange={async (enabled) => {
if (enabled) {
const confirmed = await modalConfirm({
title: t('chat.settings.max_tokens.confirm'),
content: t('chat.settings.max_tokens.confirm_content'),
okButtonProps: {
danger: true
}
})
if (!confirmed) return
}
setEnableMaxTokens(enabled) setEnableMaxTokens(enabled)
onUpdateAssistantSettings({ enableMaxTokens: enabled }) onUpdateAssistantSettings({ enableMaxTokens: enabled })
}} }}
/> />
</Row> </Row>
<Row align="middle" gutter={20}> {enableMaxTokens && (
<Col span={21}> <Row align="middle" gutter={20}>
<Slider <Col span={24}>
disabled={!enableMaxTokens} <InputNumber
min={0} disabled={!enableMaxTokens}
max={32000} min={0}
onChange={setMaxTokens} max={10000000}
onChangeComplete={onMaxTokensChange} step={100}
value={typeof maxTokens === 'number' ? maxTokens : 0} value={maxTokens}
step={100} changeOnBlur
marks={{ onChange={onMaxTokensChange}
0: '0', style={{ width: '100%' }}
32000: t('chat.settings.max') />
}} </Col>
/> </Row>
</Col> )}
<Col span={3}>
<InputNumber
disabled={!enableMaxTokens}
min={0}
max={32000}
step={100}
value={maxTokens}
onChange={onMaxTokensChange}
controls={true}
style={{ width: '100%' }}
/>
</Col>
</Row>
</SettingContainer> </SettingContainer>
) )
} }
@ -290,6 +335,15 @@ export default class DefaultAssistantSettingsPopup {
} }
} }
const EmojiButtonWrapper = styled.div`
position: relative;
display: inline-block;
&:hover .delete-icon {
display: block !important;
}
`
const Label = styled.p` const Label = styled.p`
margin: 0; margin: 0;
font-size: 14px; font-size: 14px;

View File

@ -12,6 +12,7 @@ export function getDefaultAssistant(): Assistant {
return { return {
id: 'default', id: 'default',
name: i18n.t('chat.default.name'), name: i18n.t('chat.default.name'),
emoji: '⭐️',
prompt: '', prompt: '',
topics: [getDefaultTopic('default')], topics: [getDefaultTopic('default')],
messages: [], messages: [],
@ -110,10 +111,6 @@ export const getAssistantSettings = (assistant: Assistant): AssistantSettings =>
} }
} }
export function getAssistantNameWithAgent(agent: Agent) {
return agent.emoji ? agent.emoji + ' ' + agent.name : agent.name
}
export function getAssistantById(id: string) { export function getAssistantById(id: string) {
const assistants = store.getState().assistants.assistants const assistants = store.getState().assistants.assistants
return assistants.find((a) => a.id === id) return assistants.find((a) => a.id === id)
@ -152,7 +149,8 @@ export async function createAssistantFromAgent(agent: Agent) {
const assistant: Assistant = { const assistant: Assistant = {
...agent, ...agent,
id: assistantId, id: assistantId,
name: agent.emoji ? agent.emoji + ' ' + agent.name : agent.name, name: agent.name,
emoji: agent.emoji,
topics: [topic], topics: [topic],
model: agent.defaultModel, model: agent.defaultModel,
type: 'assistant' type: 'assistant'

View File

@ -32,7 +32,7 @@ const persistedReducer = persistReducer(
{ {
key: 'cherry-studio', key: 'cherry-studio',
storage, storage,
version: 72, version: 73,
blacklist: ['runtime'], blacklist: ['runtime'],
migrate migrate
}, },

View File

@ -5,7 +5,7 @@ import { TRANSLATE_PROMPT } from '@renderer/config/prompts'
import db from '@renderer/databases' import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { Assistant } from '@renderer/types' import { Assistant } from '@renderer/types'
import { getDefaultGroupName, runAsyncFunction, uuid } from '@renderer/utils' import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { createMigrate } from 'redux-persist' import { createMigrate } from 'redux-persist'
@ -1137,6 +1137,27 @@ const migrateConfig = {
enabled: false enabled: false
}) })
} }
state.assistants.assistants.forEach((assistant) => {
const leadingEmoji = getLeadingEmoji(assistant.name)
if (leadingEmoji) {
assistant.emoji = leadingEmoji
assistant.name = assistant.name.replace(leadingEmoji, '').trim()
}
})
state.agents.agents.forEach((agent) => {
const leadingEmoji = getLeadingEmoji(agent.name)
if (leadingEmoji) {
agent.emoji = leadingEmoji
agent.name = agent.name.replace(leadingEmoji, '').trim()
}
})
const defaultAssistantEmoji = getLeadingEmoji(state.assistants.defaultAssistant.name)
if (defaultAssistantEmoji) {
state.assistants.defaultAssistant.emoji = defaultAssistantEmoji
state.assistants.defaultAssistant.name = state.assistants.defaultAssistant.name
.replace(defaultAssistantEmoji, '')
.trim()
}
return state return state
} }
} }