feat: Enhanced UI/UX with design updates, i18n, and feature enhancements.

- Updated design styles for segmented tabs and size adjustments for assistive elements.
- Added internationalization translations for English and Chinese.
- Removed unused import and functionality for switching topics sidebar.
- Added functionality to hide or show the right sidebar in the Chat page.
- Renamed Assistants component to RightSidebar.
- Improved functionality for showing and toggling topics and settings in the input bar.
- Removed unused imports and refactored Navbar component layout.
- Updated existing right sidebar functionality to allow for custom position and show topic settings.
- Removed inline styles for width from Settings component Container styles.
- Added new features for managing topics in the home page, including drag and drop functionality, a "show all" button for viewing more topics, and improved handling of large topic lists.
This commit is contained in:
kangfenmao 2024-09-08 15:56:04 +08:00
parent 9a502b5e47
commit 200d78a140
12 changed files with 179 additions and 131 deletions

View File

@ -48,8 +48,8 @@
--status-bar-height: 40px;
--input-bar-height: 85px;
--assistants-width: 240px;
--topic-list-width: 270px;
--assistants-width: 280px;
--topic-list-width: 280px;
--settings-width: var(--assistants-width);
}
@ -208,3 +208,23 @@ body,
.ant-drawer-header {
-webkit-app-region: no-drag;
}
.segmented-tab {
.ant-segmented-item-label {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
font-size: 13px;
}
.iconfont {
font-size: 13px;
margin-left: -2px;
}
.anticon-setting {
font-size: 12px;
}
.ant-segmented-item-icon + * {
margin-left: 4px;
}
}

View File

@ -3,6 +3,7 @@ import systemAgents from '@renderer/config/agents.json'
import { useAgents } from '@renderer/hooks/useAgents'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { covertAgentToAssistant } from '@renderer/services/assistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Agent, Assistant } from '@renderer/types'
import { Input, Modal, Tag } from 'antd'
import { useMemo, useState } from 'react'
@ -50,6 +51,7 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
const assistant = covertAgentToAssistant(agent)
addAssistant(assistant)
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_ASSISTANTS), 0)
resolve(assistant)
setOpen(false)
}

View File

@ -37,7 +37,9 @@ const resources = {
add: 'Add',
added: 'Added',
manage: 'Manage',
select_model: 'Select Model'
select_model: 'Select Model',
'show.all': 'Show All',
collapse: 'Collapse'
},
message: {
copied: 'Copied!',
@ -288,7 +290,9 @@ const resources = {
add: '添加',
added: '已添加',
manage: '管理',
select_model: '选择模型'
select_model: '选择模型',
'show.all': '显示全部',
collapse: '收起'
},
message: {
copied: '已复制',

View File

@ -3,7 +3,6 @@ import DragableList from '@renderer/components/DragableList'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
import { useAssistant, useAssistants } from '@renderer/hooks/useAssistant'
import { useShowTopics } from '@renderer/hooks/useStore'
import { getDefaultTopic, syncAsistantToAgent } from '@renderer/services/assistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { useAppSelector } from '@renderer/store'
@ -26,7 +25,6 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
const { assistants, removeAssistant, addAssistant, updateAssistants } = useAssistants()
const generating = useAppSelector((state) => state.runtime.generating)
const { updateAssistant, removeAllTopics } = useAssistant(activeAssistant.id)
const { showTopics, toggleShowTopics } = useShowTopics()
const { t } = useTranslation()
const onDelete = useCallback(
@ -100,7 +98,6 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
})
}
EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)
setActiveAssistant(assistant)
},
[generating, setActiveAssistant, t]
@ -116,8 +113,8 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
<AssistantItem onClick={() => onSwitchAssistant(assistant)} className={isCurrent ? 'active' : ''}>
<AssistantName className="name">{assistant.name || t('chat.default.name')}</AssistantName>
<ArrowRightButton
className={`arrow-button ${isCurrent && showTopics ? 'active' : ''}`}
onClick={() => isCurrent && toggleShowTopics()}>
className={`arrow-button ${isCurrent ? 'active' : ''}`}
onClick={() => EventEmitter.emit(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR)}>
<i className="iconfont icon-gridlines" />
</ArrowRightButton>
{false && <TopicCount className="topics-count">{assistant.topics.length}</TopicCount>}
@ -133,9 +130,6 @@ const Assistants: FC<Props> = ({ activeAssistant, setActiveAssistant, onCreateAs
const Container = styled.div`
display: flex;
flex-direction: column;
min-width: var(--assistants-width);
max-width: var(--assistants-width);
border-right: 0.5px solid var(--color-border);
height: calc(100vh - var(--navbar-height));
overflow-y: auto;
padding-top: 10px;

View File

@ -1,5 +1,6 @@
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore'
import { Assistant, Topic } from '@renderer/types'
import { Flex } from 'antd'
import { FC } from 'react'
@ -13,23 +14,28 @@ interface Props {
assistant: Assistant
activeTopic: Topic
setActiveTopic: (topic: Topic) => void
setActiveAssistant: (assistant: Assistant) => void
}
const Chat: FC<Props> = (props) => {
const { assistant } = useAssistant(props.assistant.id)
const { topicPosition } = useSettings()
const { showTopics } = useShowTopics()
return (
<Container id="chat">
{topicPosition === 'left' && (
<RightSidebar assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
)}
<Main vertical flex={1} justify="space-between">
<Messages assistant={assistant} topic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
</Main>
{topicPosition === 'right' && (
<RightSidebar assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
{topicPosition === 'right' && showTopics && (
<RightSidebar
activeAssistant={assistant}
activeTopic={props.activeTopic}
setActiveAssistant={props.setActiveAssistant}
setActiveTopic={props.setActiveTopic}
position="right"
/>
)}
</Container>
)

View File

@ -1,54 +1,48 @@
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { useAssistants } from '@renderer/hooks/useAssistant'
import { useShowAssistants } from '@renderer/hooks/useStore'
import { useActiveTopic } from '@renderer/hooks/useTopic'
import { Assistant, Topic } from '@renderer/types'
import { uuid } from '@renderer/utils'
import { FC, useState } from 'react'
import styled from 'styled-components'
import Assistants from './Assistants'
import Chat from './Chat'
import Navbar from './Navbar'
import RightSidebar from './RightSidebar'
let _activeAssistant: Assistant
const HomePage: FC = () => {
const { assistants, addAssistant } = useAssistants()
const { assistants } = useAssistants()
const [activeAssistant, setActiveAssistant] = useState(_activeAssistant || assistants[0])
const { showAssistants } = useShowAssistants()
const { defaultAssistant } = useDefaultAssistant()
const { activeTopic, setActiveTopic } = useActiveTopic(activeAssistant)
_activeAssistant = activeAssistant
const onCreateDefaultAssistant = () => {
const assistant = { ...defaultAssistant, id: uuid() }
addAssistant(assistant)
setActiveAssistant(assistant)
}
const onSetActiveTopic = (topic: Topic) => {
setActiveTopic(topic)
}
return (
<Container>
<Navbar
activeAssistant={activeAssistant}
setActiveAssistant={setActiveAssistant}
activeTopic={activeTopic}
setActiveTopic={onSetActiveTopic}
/>
<Navbar activeAssistant={activeAssistant} setActiveAssistant={setActiveAssistant} activeTopic={activeTopic} />
<ContentContainer>
{showAssistants && (
<Assistants
<RightSidebar
activeAssistant={activeAssistant}
activeTopic={activeTopic}
setActiveAssistant={setActiveAssistant}
onCreateAssistant={onCreateDefaultAssistant}
setActiveTopic={setActiveTopic}
position="left"
/>
)}
<Chat assistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={onSetActiveTopic} />
<Chat
assistant={activeAssistant}
activeTopic={activeTopic}
setActiveTopic={onSetActiveTopic}
setActiveAssistant={setActiveAssistant}
/>
</ContentContainer>
</Container>
)

View File

@ -10,6 +10,7 @@ import {
} from '@ant-design/icons'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore'
import { getDefaultTopic } from '@renderer/services/assistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { estimateInputTokenCount } from '@renderer/services/messages'
@ -48,6 +49,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const [files, setFiles] = useState<File[]>([])
const { t } = useTranslation()
const containerRef = useRef(null)
const { showTopics, toggleShowTopics } = useShowTopics()
_text = text
@ -235,12 +237,22 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
</Popconfirm>
</Tooltip>
<Tooltip placement="top" title={t('chat.input.topics')} arrow>
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)}>
<ToolbarButton
type="text"
onClick={() => {
!showTopics && toggleShowTopics()
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR), 0)
}}>
<HistoryOutlined />
</ToolbarButton>
</Tooltip>
<Tooltip placement="top" title={t('chat.input.settings')} arrow>
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS)}>
<ToolbarButton
type="text"
onClick={() => {
!showTopics && toggleShowTopics()
setTimeout(() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS), 0)
}}>
<ControlOutlined />
</ToolbarButton>
</Tooltip>

View File

@ -1,5 +1,4 @@
import { FormOutlined } from '@ant-design/icons'
import { Navbar, NavbarCenter, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import { Navbar, NavbarLeft, NavbarRight } from '@renderer/components/app/Navbar'
import { HStack } from '@renderer/components/Layout'
import AddAssistantPopup from '@renderer/components/Popups/AddAssistantPopup'
import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingPopup'
@ -8,11 +7,9 @@ import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { getDefaultTopic } from '@renderer/services/assistant'
import { Assistant, Topic } from '@renderer/types'
import { Switch } from 'antd'
import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { FC } from 'react'
import styled from 'styled-components'
import SelectModelButton from './components/SelectModelButton'
@ -21,32 +18,30 @@ interface Props {
activeAssistant: Assistant
activeTopic: Topic
setActiveAssistant: (assistant: Assistant) => void
setActiveTopic: (topic: Topic) => void
}
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiveTopic }) => {
const { assistant, addTopic } = useAssistant(activeAssistant.id)
const { t } = useTranslation()
const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant }) => {
const { assistant } = useAssistant(activeAssistant.id)
const { showAssistants, toggleShowAssistants } = useShowAssistants()
const { showTopics, toggleShowTopics } = useShowTopics()
const { theme, toggleTheme } = useTheme()
const { topicPosition } = useSettings()
const { showTopics, toggleShowTopics } = useShowTopics()
const onCreateAssistant = async () => {
const assistant = await AddAssistantPopup.show()
assistant && setActiveAssistant(assistant)
}
const addNewTopic = useCallback(() => {
const topic = getDefaultTopic()
addTopic(topic)
setActiveTopic(topic)
}, [addTopic, setActiveTopic])
return (
<Navbar>
{showAssistants && (
<NavbarLeft style={{ justifyContent: 'space-between', borderRight: 'none', padding: '0 8px' }}>
<NavbarLeft
style={{
justifyContent: 'space-between',
borderRight: 'none',
padding: '0 8px',
width: topicPosition === 'left' ? '300px' : 'var(--assistants-width)'
}}>
<NewButton onClick={toggleShowAssistants} style={{ marginLeft: isMac ? 8 : 0 }}>
<i className="iconfont icon-hide-sidebar" />
</NewButton>
@ -55,34 +50,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
</NewButton>
</NavbarLeft>
)}
{showTopics && topicPosition === 'left' && (
<NavbarCenter
style={{
paddingLeft: isMac && !showAssistants ? 16 : 8,
paddingRight: 8,
maxWidth: 'var(--topic-list-width)',
justifyContent: 'space-between'
}}>
<HStack alignItems="center">
{!showAssistants && (
<NewButton onClick={toggleShowAssistants} style={{ marginRight: isMac ? 8 : 25 }}>
<i className="iconfont icon-show-sidebar" />
</NewButton>
)}
{showAssistants && (
<TitleText>
{t('chat.topics.title')} ({assistant.topics.length})
</TitleText>
)}
</HStack>
<NewButton onClick={addNewTopic}>
<FormOutlined />
</NewButton>
</NavbarCenter>
)}
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 140 : 12, flex: 1 }}>
<HStack alignItems="center">
{!showAssistants && (topicPosition === 'left' ? !showTopics : true) && (
{!showAssistants && (
<NewButton
onClick={() => toggleShowAssistants()}
style={{ marginRight: isMac ? 8 : 25, marginLeft: isMac ? 4 : 0 }}>

View File

@ -1,78 +1,108 @@
import { BarsOutlined, SettingOutlined } from '@ant-design/icons'
import { useAssistants, useDefaultAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Assistant, Topic } from '@renderer/types'
import { Segmented } from 'antd'
import { uuid } from '@renderer/utils'
import { Segmented, SegmentedProps } from 'antd'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import Assistants from './Assistants'
import Settings from './Settings'
import Topics from './Topics'
interface Props {
assistant: Assistant
activeAssistant: Assistant
activeTopic: Topic
setActiveAssistant: (assistant: Assistant) => void
setActiveTopic: (topic: Topic) => void
position: 'left' | 'right'
}
const RightSidebar: FC<Props> = (props) => {
const [tab, setTab] = useState<'topic' | 'settings'>('topic')
const { showTopics, setShowTopics } = useShowTopics()
const RightSidebar: FC<Props> = ({ activeAssistant, activeTopic, setActiveAssistant, setActiveTopic, position }) => {
const { addAssistant } = useAssistants()
const [tab, setTab] = useState<'assistants' | 'topic' | 'settings'>(position === 'left' ? 'assistants' : 'topic')
const { topicPosition } = useSettings()
const { defaultAssistant } = useDefaultAssistant()
const { toggleShowTopics } = useShowTopics()
const { t } = useTranslation()
const isTopicTab = tab === 'topic'
const isSettingsTab = tab === 'settings'
const borderStyle = '0.5px solid var(--color-border)'
const border = position === 'left' ? { borderRight: borderStyle } : { borderLeft: borderStyle }
const showTab = !(position === 'left' && topicPosition === 'right')
const assistantTab = {
label: t('common.assistant'),
value: 'assistants',
icon: <i className="iconfont icon-business-smart-assistant" />
}
const onCreateDefaultAssistant = () => {
const assistant = { ...defaultAssistant, id: uuid() }
addAssistant(assistant)
setActiveAssistant(assistant)
}
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SHOW_ASSISTANTS, (): any => {
showTab && setTab('assistants')
}),
EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => {
if (showTopics && isTopicTab) {
return setShowTopics(false)
}
if (showTopics) {
return setTab('topic')
}
setShowTopics(true)
setTab('topic')
showTab && setTab('topic')
}),
EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => {
if (showTopics && isSettingsTab) {
return setShowTopics(false)
}
if (showTopics) {
return setTab('settings')
}
setShowTopics(true)
setTab('settings')
showTab && setTab('settings')
}),
EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => setTab('topic'))
EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => {
showTab && setTab('topic')
if (position === 'left' && topicPosition === 'right') {
toggleShowTopics()
}
})
]
return () => unsubscribes.forEach((unsub) => unsub())
}, [isSettingsTab, isTopicTab, showTopics, setShowTopics])
if (!showTopics) {
return null
}
}, [position, showTab, tab, toggleShowTopics, topicPosition])
return (
<Container style={topicPosition === 'left' ? { borderRight: borderStyle } : { borderLeft: borderStyle }}>
<Container style={{ ...border, width: topicPosition === 'left' ? '300px' : 'var(--assistants-width)' }}>
{showTab && (
<Segmented
value={tab}
style={{ borderRadius: 0, padding: '10px', gap: 5, borderBottom: '0.5px solid var(--color-border)' }}
options={[
className="segmented-tab"
style={{
borderRadius: 0,
padding: '10px',
gap: 5,
borderBottom: '0.5px solid var(--color-border)'
}}
options={
[
position === 'left' && topicPosition === 'left' ? assistantTab : undefined,
{ label: t('common.topics'), value: 'topic', icon: <BarsOutlined /> },
{ label: t('settings.title'), value: 'settings', icon: <SettingOutlined /> }
]}
block
].filter(Boolean) as SegmentedProps['options']
}
onChange={(value) => setTab(value as 'topic' | 'settings')}
block
/>
)}
<TabContent>
{tab === 'topic' && <Topics {...props} />}
{tab === 'settings' && <Settings assistant={props.assistant} />}
{tab === 'assistants' && (
<Assistants
activeAssistant={activeAssistant}
setActiveAssistant={setActiveAssistant}
onCreateAssistant={onCreateDefaultAssistant}
/>
)}
{tab === 'topic' && (
<Topics assistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
)}
{tab === 'settings' && <Settings assistant={activeAssistant} />}
</TabContent>
</Container>
)

View File

@ -236,8 +236,6 @@ const Container = styled.div`
flex-direction: column;
overflow: hidden;
padding-bottom: 10px;
min-width: var(--topic-list-width);
max-width: var(--topic-list-width);
padding: 10px 15px;
`

View File

@ -6,8 +6,9 @@ import { fetchMessagesSummary } from '@renderer/services/api'
import LocalStorage from '@renderer/services/storage'
import { useAppSelector } from '@renderer/store'
import { Assistant, Topic } from '@renderer/types'
import { Dropdown, MenuProps } from 'antd'
import { FC, useCallback } from 'react'
import { Button, Dropdown, MenuProps } from 'antd'
import { take } from 'lodash'
import { FC, useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -19,6 +20,8 @@ interface Props {
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
const { assistant, removeTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
const [showAll, setShowAll] = useState(false)
const [draging, setDraging] = useState(false)
const { t } = useTranslation()
const generating = useAppSelector((state) => state.runtime.generating)
@ -89,7 +92,11 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
return (
<Container>
<DragableList list={assistant.topics} onUpdate={updateTopics}>
<DragableList
list={take(assistant.topics, showAll ? assistant.topics.length : 15)}
onUpdate={updateTopics}
onDragStart={() => setDraging(true)}
onDragEnd={() => setDraging(false)}>
{(topic) => {
const isActive = topic.id === activeTopic?.id
return (
@ -101,6 +108,13 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
)
}}
</DragableList>
{!draging && assistant.topics.length > 15 && (
<Footer>
<Button type="link" onClick={() => setShowAll(!showAll)}>
{showAll ? t('button.collapse') : t('button.show.all')}
</Button>
</Footer>
)}
</Container>
)
}
@ -110,8 +124,6 @@ const Container = styled.div`
flex: 1;
flex-direction: column;
padding-top: 10px;
min-width: var(--topic-list-width);
max-width: var(--topic-list-width);
overflow-y: scroll;
height: calc(100vh - var(--navbar-height));
`
@ -135,4 +147,9 @@ const TopicListItem = styled.div`
}
`
const Footer = styled.div`
margin: 0 4px;
margin-bottom: 10px;
`
export default Topics

View File

@ -12,6 +12,7 @@ export const EVENT_NAMES = {
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED',
ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT',
SHOW_ASSISTANTS: 'SHOW_ASSISTANTS',
SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS',
SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR',
SWITCH_TOPIC_SIDEBAR: 'SWITCH_TOPIC_SIDEBAR',