feat(Settings): implement assistant icon type selection and localization updates

This commit is contained in:
kangfenmao 2025-04-13 10:45:47 +08:00
parent 97ef7016d3
commit 41981acd77
11 changed files with 112 additions and 34 deletions

View File

@ -1,10 +1,11 @@
import store, { useAppDispatch, useAppSelector } from '@renderer/store' import store, { useAppDispatch, useAppSelector } from '@renderer/store'
import { import {
AssistantIconType,
SendMessageShortcut, SendMessageShortcut,
setAssistantIconType,
setLaunchOnBoot, setLaunchOnBoot,
setLaunchToTray, setLaunchToTray,
setSendMessageShortcut as _setSendMessageShortcut, setSendMessageShortcut as _setSendMessageShortcut,
setShowAssistantIcon,
setSidebarIcons, setSidebarIcons,
setTargetLanguage, setTargetLanguage,
setTheme, setTheme,
@ -70,8 +71,8 @@ export function useSettings() {
updateSidebarDisabledIcons(icons: SidebarIcon[]) { updateSidebarDisabledIcons(icons: SidebarIcon[]) {
dispatch(setSidebarIcons({ disabled: icons })) dispatch(setSidebarIcons({ disabled: icons }))
}, },
setShowAssistantIcon(showAssistantIcon: boolean) { setAssistantIconType(assistantIconType: AssistantIconType) {
dispatch(setShowAssistantIcon(showAssistantIcon)) dispatch(setAssistantIconType(assistantIconType))
} }
} }
} }

View File

@ -43,6 +43,7 @@
"edit.title": "Edit Assistant", "edit.title": "Edit Assistant",
"save.success": "Saved successfully", "save.success": "Saved successfully",
"save.title": "Save to agent", "save.title": "Save to agent",
"icon.type": "Assistant Icon",
"search": "Search assistants...", "search": "Search assistants...",
"settings.default_model": "Default Model", "settings.default_model": "Default Model",
"settings.knowledge_base": "Knowledge Base Settings", "settings.knowledge_base": "Knowledge Base Settings",
@ -787,7 +788,10 @@
"advanced.title": "Advanced Settings", "advanced.title": "Advanced Settings",
"assistant": "Default Assistant", "assistant": "Default Assistant",
"assistant.model_params": "Model Parameters", "assistant.model_params": "Model Parameters",
"assistant.show.icon": "Show model icon", "assistant.icon.type": "Model Icon Type",
"assistant.icon.type.model": "Model Icon",
"assistant.icon.type.emoji": "Emoji Icon",
"assistant.icon.type.none": "Hide",
"assistant.title": "Default Assistant", "assistant.title": "Default Assistant",
"data": { "data": {
"app_data": "App Data", "app_data": "App Data",

View File

@ -43,6 +43,7 @@
"edit.title": "アシスタントを編集", "edit.title": "アシスタントを編集",
"save.success": "保存に成功しました", "save.success": "保存に成功しました",
"save.title": "エージェントに保存", "save.title": "エージェントに保存",
"icon.type": "アシスタントアイコン",
"search": "アシスタントを検索...", "search": "アシスタントを検索...",
"settings.mcp": "MCP サーバー", "settings.mcp": "MCP サーバー",
"settings.mcp.enableFirst": "まず MCP 設定でこのサーバーを有効にしてください", "settings.mcp.enableFirst": "まず MCP 設定でこのサーバーを有効にしてください",
@ -787,7 +788,10 @@
"advanced.title": "詳細設定", "advanced.title": "詳細設定",
"assistant": "デフォルトアシスタント", "assistant": "デフォルトアシスタント",
"assistant.model_params": "モデルパラメータ", "assistant.model_params": "モデルパラメータ",
"assistant.show.icon": "モデルアイコンを表示", "assistant.icon.type": "モデルアイコンタイプ",
"assistant.icon.type.model": "モデルアイコン",
"assistant.icon.type.emoji": "Emoji アイコン",
"assistant.icon.type.none": "表示しない",
"assistant.title": "デフォルトアシスタント", "assistant.title": "デフォルトアシスタント",
"data": { "data": {
"app_data": "アプリデータ", "app_data": "アプリデータ",

View File

@ -43,6 +43,7 @@
"edit.title": "Редактировать ассистента", "edit.title": "Редактировать ассистента",
"save.success": "Успешно сохранено", "save.success": "Успешно сохранено",
"save.title": "Сохранить в агента", "save.title": "Сохранить в агента",
"icon.type": "Иконка ассистента",
"search": "Поиск ассистентов...", "search": "Поиск ассистентов...",
"settings.mcp": "Серверы MCP", "settings.mcp": "Серверы MCP",
"settings.mcp.enableFirst": "Сначала включите этот сервер в настройках MCP", "settings.mcp.enableFirst": "Сначала включите этот сервер в настройках MCP",
@ -787,7 +788,10 @@
"advanced.title": "Расширенные настройки", "advanced.title": "Расширенные настройки",
"assistant": "Ассистент по умолчанию", "assistant": "Ассистент по умолчанию",
"assistant.model_params": "Параметры модели", "assistant.model_params": "Параметры модели",
"assistant.show.icon": "Показывать модельный иконки", "assistant.icon.type": "Тип модели иконки",
"assistant.icon.type.model": "Модель иконки",
"assistant.icon.type.emoji": "Emoji иконка",
"assistant.icon.type.none": "Не отображать",
"assistant.title": "Ассистент по умолчанию", "assistant.title": "Ассистент по умолчанию",
"data": { "data": {
"app_data": "Данные приложения", "app_data": "Данные приложения",

View File

@ -43,6 +43,7 @@
"edit.title": "编辑助手", "edit.title": "编辑助手",
"save.success": "保存成功", "save.success": "保存成功",
"save.title": "保存到智能体", "save.title": "保存到智能体",
"icon.type": "助手图标",
"search": "搜索助手", "search": "搜索助手",
"settings.mcp": "MCP 服务器", "settings.mcp": "MCP 服务器",
"settings.mcp.enableFirst": "请先在 MCP 设置中启用此服务器", "settings.mcp.enableFirst": "请先在 MCP 设置中启用此服务器",
@ -787,7 +788,10 @@
"advanced.title": "高级设置", "advanced.title": "高级设置",
"assistant": "默认助手", "assistant": "默认助手",
"assistant.model_params": "模型参数", "assistant.model_params": "模型参数",
"assistant.show.icon": "显示模型图标", "assistant.icon.type": "模型图标类型",
"assistant.icon.type.model": "模型图标",
"assistant.icon.type.emoji": "Emoji 表情",
"assistant.icon.type.none": "不显示",
"assistant.title": "默认助手", "assistant.title": "默认助手",
"data": { "data": {
"app_data": "应用数据", "app_data": "应用数据",

View File

@ -43,6 +43,7 @@
"edit.title": "編輯助手", "edit.title": "編輯助手",
"save.success": "儲存成功", "save.success": "儲存成功",
"save.title": "儲存到智慧代理人", "save.title": "儲存到智慧代理人",
"icon.type": "助手圖示",
"search": "搜尋助手...", "search": "搜尋助手...",
"settings.mcp": "MCP 伺服器", "settings.mcp": "MCP 伺服器",
"settings.mcp.enableFirst": "請先在 MCP 設定中啟用此伺服器", "settings.mcp.enableFirst": "請先在 MCP 設定中啟用此伺服器",
@ -787,7 +788,10 @@
"advanced.title": "進階設定", "advanced.title": "進階設定",
"assistant": "預設助手", "assistant": "預設助手",
"assistant.model_params": "模型參數", "assistant.model_params": "模型參數",
"assistant.show.icon": "顯示模型圖示", "assistant.icon.type": "模型圖示類型",
"assistant.icon.type.model": "模型圖示",
"assistant.icon.type.emoji": "Emoji 表情",
"assistant.icon.type.none": "不顯示",
"assistant.title": "預設助手", "assistant.title": "預設助手",
"data": { "data": {
"app_data": "應用程式資料", "app_data": "應用程式資料",

View File

@ -3,6 +3,7 @@ import {
EditOutlined, EditOutlined,
MinusCircleOutlined, MinusCircleOutlined,
SaveOutlined, SaveOutlined,
SmileOutlined,
SortAscendingOutlined, SortAscendingOutlined,
SortDescendingOutlined SortDescendingOutlined
} from '@ant-design/icons' } from '@ant-design/icons'
@ -39,7 +40,7 @@ interface AssistantItemProps {
const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch, onDelete, addAgent, addAssistant }) => { const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch, onDelete, addAgent, addAssistant }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID const { removeAllTopics } = useAssistant(assistant.id) // 使用当前助手的ID
const { clickAssistantToShowTopic, topicPosition, showAssistantIcon } = useSettings() const { clickAssistantToShowTopic, topicPosition, assistantIconType, setAssistantIconType } = useSettings()
const defaultModel = getDefaultModel() const defaultModel = getDefaultModel()
const { assistants, updateAssistants } = useAssistants() const { assistants, updateAssistants } = useAssistants()
@ -119,6 +120,28 @@ const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch,
}) })
} }
}, },
{
label: t('assistants.icon.type'),
key: 'icon-type',
icon: <SmileOutlined />,
children: [
{
label: t('settings.assistant.icon.type.model'),
key: 'model',
onClick: () => setAssistantIconType('model')
},
{
label: t('settings.assistant.icon.type.emoji'),
key: 'emoji',
onClick: () => setAssistantIconType('emoji')
},
{
label: t('settings.assistant.icon.type.none'),
key: 'none',
onClick: () => setAssistantIconType('none')
}
]
},
{ type: 'divider' }, { type: 'divider' },
{ {
label: t('common.sort.pinyin.asc'), label: t('common.sort.pinyin.asc'),
@ -174,18 +197,20 @@ const AssistantItem: FC<AssistantItemProps> = ({ assistant, isActive, onSwitch,
<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' : ''}>
<AssistantNameRow className="name" title={fullAssistantName}> <AssistantNameRow className="name" title={fullAssistantName}>
{showAssistantIcon ? ( {assistantIconType === 'model' ? (
<ModelAvatar <ModelAvatar
model={assistant.model || defaultModel} model={assistant.model || defaultModel}
size={22} size={24}
className={isPending && !isActive ? 'animation-pulse' : ''} className={isPending && !isActive ? 'animation-pulse' : ''}
/> />
) : ( ) : (
<AssistantEmoji assistantIconType === 'emoji' && (
$emoji={assistant.emoji || assistantName.slice(0, 1)} <AssistantEmoji
className={isPending && !isActive ? 'animation-pulse' : ''}> $emoji={assistant.emoji || assistantName.slice(0, 1)}
{assistant.emoji || assistantName.slice(0, 1)} className={isPending && !isActive ? 'animation-pulse' : ''}>
</AssistantEmoji> {assistant.emoji || assistantName.slice(0, 1)}
</AssistantEmoji>
)
)} )}
<AssistantName className="text-nowrap">{assistantName}</AssistantName> <AssistantName className="text-nowrap">{assistantName}</AssistantName>
</AssistantNameRow> </AssistantNameRow>
@ -203,7 +228,8 @@ const Container = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
padding: 7px 10px; padding: 0 10px;
height: 37px;
position: relative; position: relative;
font-family: Ubuntu; font-family: Ubuntu;
border-radius: var(--list-item-border-radius); border-radius: var(--list-item-border-radius);
@ -231,20 +257,21 @@ const AssistantNameRow = styled.div`
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
gap: 5px; gap: 8px;
` `
const AssistantEmoji = styled.div<{ $emoji: string }>` const AssistantEmoji = styled.div<{ $emoji: string }>`
width: 22px; width: 26px;
height: 22px; height: 26px;
border-radius: 11px; border-radius: 13px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
font-size: 12px; font-size: 15px;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
margin-right: 3px;
&:before { &:before {
width: 100%; width: 100%;
height: 100%; height: 100%;
@ -261,7 +288,9 @@ const AssistantEmoji = styled.div<{ $emoji: string }>`
} }
` `
const AssistantName = styled.div`` const AssistantName = styled.div`
font-size: 13px;
`
const MenuButton = styled.div` const MenuButton = styled.div`
display: flex; display: flex;

View File

@ -4,7 +4,9 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { import {
AssistantIconType,
DEFAULT_SIDEBAR_ICONS, DEFAULT_SIDEBAR_ICONS,
setAssistantIconType,
setClickAssistantToShowTopic, setClickAssistantToShowTopic,
setCustomCss, setCustomCss,
setShowTopicTime, setShowTopicTime,
@ -31,8 +33,7 @@ const DisplaySettings: FC = () => {
showTopicTime, showTopicTime,
customCss, customCss,
sidebarIcons, sidebarIcons,
showAssistantIcon, assistantIconType
setShowAssistantIcon
} = useSettings() } = useSettings()
const { theme: themeMode } = useTheme() const { theme: themeMode } = useTheme()
const { t } = useTranslation() const { t } = useTranslation()
@ -87,6 +88,15 @@ const DisplaySettings: FC = () => {
[t] [t]
) )
const assistantIconTypeOptions = useMemo(
() => [
{ value: 'model', label: t('settings.assistant.icon.type.model') },
{ value: 'emoji', label: t('settings.assistant.icon.type.emoji') },
{ value: 'none', label: t('settings.assistant.icon.type.none') }
],
[t]
)
return ( return (
<SettingContainer theme={themeMode}> <SettingContainer theme={themeMode}>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>
@ -143,8 +153,13 @@ const DisplaySettings: FC = () => {
<SettingTitle>{t('settings.display.assistant.title')}</SettingTitle> <SettingTitle>{t('settings.display.assistant.title')}</SettingTitle>
<SettingDivider /> <SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.assistant.show.icon')}</SettingRowTitle> <SettingRowTitle>{t('settings.assistant.icon.type')}</SettingRowTitle>
<Switch checked={showAssistantIcon} onChange={(checked) => setShowAssistantIcon(checked)} /> <Segmented
value={assistantIconType}
shape="round"
onChange={(value) => dispatch(setAssistantIconType(value as AssistantIconType))}
options={assistantIconTypeOptions}
/>
</SettingRow> </SettingRow>
</SettingGroup> </SettingGroup>
<SettingGroup theme={theme}> <SettingGroup theme={theme}>

View File

@ -42,7 +42,7 @@ const persistedReducer = persistReducer(
{ {
key: 'cherry-studio', key: 'cherry-studio',
storage, storage,
version: 95, version: 96,
blacklist: ['runtime', 'messages'], blacklist: ['runtime', 'messages'],
migrate migrate
}, },

View File

@ -1213,6 +1213,17 @@ const migrateConfig = {
} catch (error) { } catch (error) {
return state return state
} }
},
'96': (state: RootState) => {
try {
// @ts-ignore eslint-disable-next-line
state.settings.assistantIconType = state.settings?.showAssistantIcon ? 'model' : 'emoji'
// @ts-ignore eslint-disable-next-line
delete state.settings.showAssistantIcon
return state
} catch (error) {
return state
}
} }
} }

View File

@ -21,6 +21,8 @@ export const DEFAULT_SIDEBAR_ICONS: SidebarIcon[] = [
export interface NutstoreSyncRuntime extends WebDAVSyncState {} export interface NutstoreSyncRuntime extends WebDAVSyncState {}
export type AssistantIconType = 'model' | 'emoji' | 'none'
export interface SettingsState { export interface SettingsState {
showAssistants: boolean showAssistants: boolean
showTopics: boolean showTopics: boolean
@ -42,7 +44,7 @@ export interface SettingsState {
fontSize: number fontSize: number
topicPosition: 'left' | 'right' topicPosition: 'left' | 'right'
showTopicTime: boolean showTopicTime: boolean
showAssistantIcon: boolean assistantIconType: AssistantIconType
pasteLongTextAsFile: boolean pasteLongTextAsFile: boolean
pasteLongTextThreshold: number pasteLongTextThreshold: number
clickAssistantToShowTopic: boolean clickAssistantToShowTopic: boolean
@ -147,7 +149,7 @@ export const initialState: SettingsState = {
fontSize: 14, fontSize: 14,
topicPosition: 'left', topicPosition: 'left',
showTopicTime: false, showTopicTime: false,
showAssistantIcon: false, assistantIconType: 'emoji',
pasteLongTextAsFile: false, pasteLongTextAsFile: false,
pasteLongTextThreshold: 1500, pasteLongTextThreshold: 1500,
clickAssistantToShowTopic: true, clickAssistantToShowTopic: true,
@ -294,8 +296,8 @@ const settingsSlice = createSlice({
setShowTopicTime: (state, action: PayloadAction<boolean>) => { setShowTopicTime: (state, action: PayloadAction<boolean>) => {
state.showTopicTime = action.payload state.showTopicTime = action.payload
}, },
setShowAssistantIcon: (state, action: PayloadAction<boolean>) => { setAssistantIconType: (state, action: PayloadAction<AssistantIconType>) => {
state.showAssistantIcon = action.payload state.assistantIconType = action.payload
}, },
setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => { setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => {
state.pasteLongTextAsFile = action.payload state.pasteLongTextAsFile = action.payload
@ -508,7 +510,7 @@ export const {
setWindowStyle, setWindowStyle,
setTopicPosition, setTopicPosition,
setShowTopicTime, setShowTopicTime,
setShowAssistantIcon, setAssistantIconType,
setPasteLongTextAsFile, setPasteLongTextAsFile,
setAutoCheckUpdate, setAutoCheckUpdate,
setRenderInputMessageAsMarkdown, setRenderInputMessageAsMarkdown,