mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-09 06:49:02 +08:00
feat: improved ui effects and rendering for components
- Added smooth all property transition effect to Icon component. - Added hover effect and conditional rendering for Switch Topic Sidebar button on current assistant. - Updated the existing conditional options array to consistently include both topic and settings options. - Improved hover effects on topic list items.
This commit is contained in:
parent
f26ebf44c7
commit
62072e4f66
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
--color-background: #181818;
|
--color-background: #181818;
|
||||||
--color-background-soft: var(--color-black-soft);
|
--color-background-soft: var(--color-black-soft);
|
||||||
--color-background-mute: var(--color-black-mute);
|
--color-background-mute: var(--color-black-soft);
|
||||||
|
|
||||||
--color-primary: #00b96b;
|
--color-primary: #00b96b;
|
||||||
--color-primary-soft: #00b96b99;
|
--color-primary-soft: #00b96b99;
|
||||||
|
|||||||
@ -48,12 +48,12 @@ const Sidebar: FC = () => {
|
|||||||
<Menus onClick={MinApp.onClose}>
|
<Menus onClick={MinApp.onClose}>
|
||||||
<StyledLink onClick={() => to('/')}>
|
<StyledLink onClick={() => to('/')}>
|
||||||
<Icon className={isRoute('/')}>
|
<Icon className={isRoute('/')}>
|
||||||
<i className="iconfont icon-chat"></i>
|
<i className="iconfont icon-chat" />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={() => to('/agents')}>
|
<StyledLink onClick={() => to('/agents')}>
|
||||||
<Icon className={isRoute('/agents')}>
|
<Icon className={isRoute('/agents')}>
|
||||||
<i className="iconfont icon-business-smart-assistant"></i>
|
<i className="iconfont icon-business-smart-assistant" />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={() => to('/translate')}>
|
<StyledLink onClick={() => to('/translate')}>
|
||||||
@ -63,7 +63,7 @@ const Sidebar: FC = () => {
|
|||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={() => to('/apps')}>
|
<StyledLink onClick={() => to('/apps')}>
|
||||||
<Icon className={isRoute('/apps')}>
|
<Icon className={isRoute('/apps')}>
|
||||||
<i className="iconfont icon-appstore"></i>
|
<i className="iconfont icon-appstore" />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<StyledLink onClick={() => to('/files')}>
|
<StyledLink onClick={() => to('/files')}>
|
||||||
@ -76,7 +76,7 @@ const Sidebar: FC = () => {
|
|||||||
<Menus onClick={MinApp.onClose}>
|
<Menus onClick={MinApp.onClose}>
|
||||||
<StyledLink onClick={() => to(isLocalAi ? '/settings/assistant' : '/settings/provider')}>
|
<StyledLink onClick={() => to(isLocalAi ? '/settings/assistant' : '/settings/provider')}>
|
||||||
<Icon className={pathname.startsWith('/settings') ? 'active' : ''}>
|
<Icon className={pathname.startsWith('/settings') ? 'active' : ''}>
|
||||||
<i className="iconfont icon-setting"></i>
|
<i className="iconfont icon-setting" />
|
||||||
</Icon>
|
</Icon>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
</Menus>
|
</Menus>
|
||||||
@ -128,6 +128,7 @@ const Icon = styled.div`
|
|||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
-webkit-app-region: none;
|
-webkit-app-region: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
.iconfont,
|
.iconfont,
|
||||||
.anticon {
|
.anticon {
|
||||||
color: var(--color-icon);
|
color: var(--color-icon);
|
||||||
@ -139,7 +140,7 @@ const Icon = styled.div`
|
|||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
}
|
}
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-soft);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
.iconfont,
|
.iconfont,
|
||||||
.anticon {
|
.anticon {
|
||||||
@ -147,7 +148,7 @@ const Icon = styled.div`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-background-soft);
|
background-color: var(--color-background-mute);
|
||||||
.iconfont,
|
.iconfont,
|
||||||
.anticon {
|
.anticon {
|
||||||
color: var(--color-icon-white);
|
color: var(--color-icon-white);
|
||||||
|
|||||||
@ -66,7 +66,7 @@ const resources = {
|
|||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
save: 'Save',
|
save: 'Save',
|
||||||
'default.name': 'Default Assistant',
|
'default.name': '⭐️ Default Assistant',
|
||||||
'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.topic.name': 'Default Topic',
|
'default.topic.name': 'Default Topic',
|
||||||
'topics.title': 'Topics',
|
'topics.title': 'Topics',
|
||||||
@ -344,7 +344,7 @@ const resources = {
|
|||||||
},
|
},
|
||||||
chat: {
|
chat: {
|
||||||
save: '保存',
|
save: '保存',
|
||||||
'default.name': '默认助手',
|
'default.name': '⭐️ 默认助手',
|
||||||
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
|
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
|
||||||
'default.topic.name': '默认话题',
|
'default.topic.name': '默认话题',
|
||||||
'topics.title': '话题',
|
'topics.title': '话题',
|
||||||
|
|||||||
@ -186,6 +186,7 @@ const Assistants: FC<Props> = ({
|
|||||||
list={list}
|
list={list}
|
||||||
onUpdate={updateAssistants}
|
onUpdate={updateAssistants}
|
||||||
droppableProps={{ isDropDisabled: !isEmpty(search) }}
|
droppableProps={{ isDropDisabled: !isEmpty(search) }}
|
||||||
|
style={{ paddingBottom: dragging ? '34px' : 0 }}
|
||||||
onDragStart={() => setDragging(true)}
|
onDragStart={() => setDragging(true)}
|
||||||
onDragEnd={() => setDragging(false)}>
|
onDragEnd={() => setDragging(false)}>
|
||||||
{(assistant) => {
|
{(assistant) => {
|
||||||
@ -194,11 +195,11 @@ const Assistants: FC<Props> = ({
|
|||||||
<Dropdown key={assistant.id} menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
|
<Dropdown key={assistant.id} menu={{ items: getMenuItems(assistant) }} trigger={['contextMenu']}>
|
||||||
<AssistantItem onClick={() => onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}>
|
<AssistantItem onClick={() => onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}>
|
||||||
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
|
||||||
<ArrowRightButton
|
{isCurrent && (
|
||||||
className={`arrow-button ${isCurrent ? 'active' : ''}`}
|
<ArrowRightButton onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
|
||||||
onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
|
<i className="iconfont icon-gridlines" />
|
||||||
<i className="iconfont icon-gridlines" />
|
</ArrowRightButton>
|
||||||
</ArrowRightButton>
|
)}
|
||||||
{false && <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>}
|
{false && <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>}
|
||||||
</AssistantItem>
|
</AssistantItem>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@ -206,10 +207,12 @@ const Assistants: FC<Props> = ({
|
|||||||
}}
|
}}
|
||||||
</DragableList>
|
</DragableList>
|
||||||
{!dragging && (
|
{!dragging && (
|
||||||
<AddButton onClick={onCreateAssistant}>
|
<AssistantItem onClick={onCreateAssistant}>
|
||||||
<AddButtonText>{t('chat.add.assistant.title')}</AddButtonText>
|
<AssistantName>
|
||||||
<PlusOutlined />
|
<PlusOutlined style={{ color: 'var(--color-text-2)', marginRight: 4 }} />
|
||||||
</AddButton>
|
{t('chat.add.assistant.title')}
|
||||||
|
</AssistantName>
|
||||||
|
</AssistantItem>
|
||||||
)}
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
@ -230,15 +233,18 @@ const AssistantItem = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
position: relative;
|
position: relative;
|
||||||
border-radius: 6px;
|
border-radius: 4px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
padding-right: 35px;
|
padding-right: 35px;
|
||||||
cursor: pointer;
|
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
|
cursor: pointer;
|
||||||
.iconfont {
|
.iconfont {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
}
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
.name {
|
.name {
|
||||||
@ -311,29 +317,4 @@ const CommandKey = styled.div`
|
|||||||
margin-right: -4px;
|
margin-right: -4px;
|
||||||
`
|
`
|
||||||
|
|
||||||
const AddButton = styled.div`
|
|
||||||
height: 34px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 6px 10px;
|
|
||||||
margin: 0 10px;
|
|
||||||
margin-top: -2px;
|
|
||||||
color: var(--color-text-2);
|
|
||||||
transition: all 0.2s ease-in-out;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 8px;
|
|
||||||
.anticon {
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
color: var(--color-text-1);
|
|
||||||
background-color: var(--color-background-soft);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
const AddButtonText = styled.span``
|
|
||||||
|
|
||||||
export default Assistants
|
export default Assistants
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ClearOutlined,
|
ClearOutlined,
|
||||||
ControlOutlined,
|
ControlOutlined,
|
||||||
|
FormOutlined,
|
||||||
FullscreenExitOutlined,
|
FullscreenExitOutlined,
|
||||||
FullscreenOutlined,
|
FullscreenOutlined,
|
||||||
PauseCircleOutlined,
|
PauseCircleOutlined,
|
||||||
@ -256,11 +257,6 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
textareaRef.current?.focus()
|
textareaRef.current?.focus()
|
||||||
}, [assistant])
|
}, [assistant])
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.addEventListener('paste', onPaste)
|
|
||||||
return () => document.removeEventListener('paste', onPaste)
|
|
||||||
}, [onPaste])
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<AttachmentPreview files={files} setFiles={setFiles} />
|
<AttachmentPreview files={files} setFiles={setFiles} />
|
||||||
@ -281,13 +277,14 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
|
|||||||
onBlur={() => setInputFocus(false)}
|
onBlur={() => setInputFocus(false)}
|
||||||
onInput={onInput}
|
onInput={onInput}
|
||||||
disabled={searching}
|
disabled={searching}
|
||||||
|
onPaste={(e) => onPaste(e.nativeEvent)}
|
||||||
onClick={() => searching && dispatch(setSearching(false))}
|
onClick={() => searching && dispatch(setSearching(false))}
|
||||||
/>
|
/>
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<ToolbarMenu>
|
<ToolbarMenu>
|
||||||
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
<Tooltip placement="top" title={t('chat.input.new_topic')} arrow>
|
||||||
<ToolbarButton type="text" onClick={addNewTopic}>
|
<ToolbarButton type="text" onClick={addNewTopic}>
|
||||||
<i className="iconfont icon-a-addchat" />
|
<FormOutlined />
|
||||||
</ToolbarButton>
|
</ToolbarButton>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
<Tooltip placement="top" title={t('chat.input.clear')} arrow>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { FormOutlined } from '@ant-design/icons'
|
||||||
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
|
||||||
import { HStack } from '@renderer/components/Layout'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
|
||||||
@ -8,6 +9,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
|
|||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
|
||||||
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
|
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
|
||||||
|
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
|
||||||
import { Assistant, Topic } from '@renderer/types'
|
import { Assistant, Topic } from '@renderer/types'
|
||||||
import { Switch } from 'antd'
|
import { Switch } from 'antd'
|
||||||
import { FC, useCallback } from 'react'
|
import { FC, useCallback } from 'react'
|
||||||
@ -41,7 +43,8 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveTopic }) => {
|
|||||||
addTopic(topic)
|
addTopic(topic)
|
||||||
setActiveTopic(topic)
|
setActiveTopic(topic)
|
||||||
db.topics.add({ id: topic.id, messages: [] })
|
db.topics.add({ id: topic.id, messages: [] })
|
||||||
window.message.success({ content: t('message.topic.added') })
|
window.message.success({ content: t('message.topic.added'), key: 'topic-added' })
|
||||||
|
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
|
||||||
}, [addTopic, setActiveTopic, t])
|
}, [addTopic, setActiveTopic, t])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,7 +55,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveTopic }) => {
|
|||||||
<i className="iconfont icon-hide-sidebar" />
|
<i className="iconfont icon-hide-sidebar" />
|
||||||
</NewButton>
|
</NewButton>
|
||||||
<NewButton onClick={addNewTopic}>
|
<NewButton onClick={addNewTopic}>
|
||||||
<i className="iconfont icon-a-addchat" />
|
<FormOutlined />
|
||||||
</NewButton>
|
</NewButton>
|
||||||
</NavbarLeft>
|
</NavbarLeft>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -102,8 +102,16 @@ const RightSidebar: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssist
|
|||||||
options={
|
options={
|
||||||
[
|
[
|
||||||
position === 'left' && topicPosition === 'left' ? assistantTab : undefined,
|
position === 'left' && topicPosition === 'left' ? assistantTab : undefined,
|
||||||
{ label: t('common.topics'), value: 'topic', icon: <BarsOutlined /> },
|
{
|
||||||
{ label: t('settings.title'), value: 'settings', icon: <SettingOutlined /> }
|
label: t('common.topics'),
|
||||||
|
value: 'topic',
|
||||||
|
icon: <BarsOutlined />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t('settings.title'),
|
||||||
|
value: 'settings',
|
||||||
|
icon: <SettingOutlined />
|
||||||
|
}
|
||||||
].filter(Boolean) as SegmentedProps['options']
|
].filter(Boolean) as SegmentedProps['options']
|
||||||
}
|
}
|
||||||
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
onChange={(value) => setTab(value as 'topic' | 'settings')}
|
||||||
|
|||||||
@ -136,8 +136,11 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
|
|||||||
return (
|
return (
|
||||||
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
|
||||||
<TopicListItem className={isActive ? 'active' : ''} onClick={() => onSwitchTopic(topic)}>
|
<TopicListItem className={isActive ? 'active' : ''} onClick={() => onSwitchTopic(topic)}>
|
||||||
<TopicName className="name">{topic.name}</TopicName>
|
<TopicName className="name">
|
||||||
{assistant.topics.length > 1 && (
|
<TopicHash>#</TopicHash>
|
||||||
|
{topic.name.replace('`', '')}
|
||||||
|
</TopicName>
|
||||||
|
{assistant.topics.length > 1 && isActive && (
|
||||||
<MenuButton
|
<MenuButton
|
||||||
className="menu"
|
className="menu"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@ -171,8 +174,7 @@ const Container = styled.div`
|
|||||||
const TopicListItem = styled.div`
|
const TopicListItem = styled.div`
|
||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
cursor: pointer;
|
border-radius: 4px;
|
||||||
border-radius: 6px;
|
|
||||||
font-family: Ubuntu;
|
font-family: Ubuntu;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -180,10 +182,18 @@ const TopicListItem = styled.div`
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
font-family: Ubuntu;
|
||||||
|
cursor: pointer;
|
||||||
.menu {
|
.menu {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
color: var(--color-text-3);
|
color: var(--color-text-3);
|
||||||
}
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
.name {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
&.active {
|
&.active {
|
||||||
background-color: var(--color-background-mute);
|
background-color: var(--color-background-mute);
|
||||||
.name {
|
.name {
|
||||||
@ -214,17 +224,20 @@ const MenuButton = styled.div`
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
width: 30px;
|
min-width: 22px;
|
||||||
height: 24px;
|
min-height: 22px;
|
||||||
min-width: 24px;
|
|
||||||
min-height: 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 10px;
|
right: 8px;
|
||||||
top: 5px;
|
top: 6px;
|
||||||
.anticon {
|
.anticon {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const TopicHash = styled.span`
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
margin-right: 2px;
|
||||||
|
`
|
||||||
|
|
||||||
export default Topics
|
export default Topics
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user