feat: change topics position

This commit is contained in:
kangfenmao 2024-09-05 22:53:52 +08:00
parent d809f50c0e
commit ab0e7e1e07
11 changed files with 103 additions and 69 deletions

View File

@ -3,6 +3,7 @@ import {
SendMessageShortcut, SendMessageShortcut,
setSendMessageShortcut as _setSendMessageShortcut, setSendMessageShortcut as _setSendMessageShortcut,
setTheme, setTheme,
setTopicPosition,
setWindowStyle, setWindowStyle,
ThemeMode ThemeMode
} from '@renderer/store/settings' } from '@renderer/store/settings'
@ -21,6 +22,9 @@ export function useSettings() {
}, },
setWindowStyle(windowStyle: 'transparent' | 'opaque') { setWindowStyle(windowStyle: 'transparent' | 'opaque') {
dispatch(setWindowStyle(windowStyle)) dispatch(setWindowStyle(windowStyle))
},
setTopicPosition(topicPosition: 'left' | 'right') {
dispatch(setTopicPosition(topicPosition))
} }
} }
} }

View File

@ -1,23 +1,5 @@
import { useAppDispatch, useAppSelector } from '@renderer/store' import { useAppDispatch, useAppSelector } from '@renderer/store'
import { import { setShowTopics, toggleShowAssistants, toggleShowTopics } from '@renderer/store/settings'
setShowRightSidebar,
setShowTopics,
toggleRightSidebar,
toggleShowAssistants,
toggleShowTopics
} from '@renderer/store/settings'
export function useShowRightSidebar() {
const showRightSidebar = useAppSelector((state) => state.settings.showRightSidebar)
const dispatch = useAppDispatch()
return {
rightSidebarShown: showRightSidebar,
toggleRightSidebar: () => dispatch(toggleRightSidebar()),
showRightSidebar: () => dispatch(setShowRightSidebar(true)),
hideRightSidebar: () => dispatch(setShowRightSidebar(false))
}
}
export function useShowAssistants() { export function useShowAssistants() {
const showAssistants = useAppSelector((state) => state.settings.showAssistants) const showAssistants = useAppSelector((state) => state.settings.showAssistants)

View File

@ -207,7 +207,10 @@ const resources = {
'theme.window.style.title': 'Window Style', 'theme.window.style.title': 'Window Style',
'theme.window.style.transparent': 'Transparent Window', 'theme.window.style.transparent': 'Transparent Window',
'theme.window.style.opaque': 'Opaque Window', 'theme.window.style.opaque': 'Opaque Window',
'font_size.title': 'Message Font Size' 'font_size.title': 'Message Font Size',
'topic.position': 'Topic Position',
'topic.position.left': 'Left',
'topic.position.right': 'Right'
}, },
translate: { translate: {
title: 'Translation', title: 'Translation',
@ -454,7 +457,10 @@ const resources = {
'theme.window.style.title': '窗口样式', 'theme.window.style.title': '窗口样式',
'theme.window.style.transparent': '透明窗口', 'theme.window.style.transparent': '透明窗口',
'theme.window.style.opaque': '不透明窗口', 'theme.window.style.opaque': '不透明窗口',
'font_size.title': '消息字体大小' 'font_size.title': '消息字体大小',
'topic.position': '话题位置',
'topic.position.left': '左侧',
'topic.position.right': '右侧'
}, },
translate: { translate: {
title: '翻译', title: '翻译',

View File

@ -1,8 +1,9 @@
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useShowRightSidebar, useShowTopics } from '@renderer/hooks/useStore' import { useSettings } from '@renderer/hooks/useSettings'
import { useShowTopics } from '@renderer/hooks/useStore'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { Flex } from 'antd' import { Flex } from 'antd'
import { FC, useEffect, useState } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import Inputbar from './Inputbar/Inputbar' import Inputbar from './Inputbar/Inputbar'
@ -17,23 +18,21 @@ interface Props {
const Chat: FC<Props> = (props) => { const Chat: FC<Props> = (props) => {
const { assistant } = useAssistant(props.assistant.id) const { assistant } = useAssistant(props.assistant.id)
const [showSetting, setShowSetting] = useState(false)
const { rightSidebarShown } = useShowRightSidebar()
const { showTopics } = useShowTopics() const { showTopics } = useShowTopics()
const { topicPosition } = useSettings()
useEffect(() => {
!rightSidebarShown && showSetting && setShowSetting(false)
}, [rightSidebarShown, showSetting])
return ( return (
<Container id="chat"> <Container id="chat">
{showTopics && ( {showTopics && topicPosition === 'left' && (
<Topics assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} /> <Topics assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
)} )}
<Main vertical flex={1} justify="space-between"> <Main vertical flex={1} justify="space-between">
<Messages assistant={assistant} topic={props.activeTopic} setActiveTopic={props.setActiveTopic} /> <Messages assistant={assistant} topic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
<Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} /> <Inputbar assistant={assistant} setActiveTopic={props.setActiveTopic} />
</Main> </Main>
{showTopics && topicPosition === 'right' && (
<Topics assistant={assistant} activeTopic={props.activeTopic} setActiveTopic={props.setActiveTopic} />
)}
</Container> </Container>
) )
} }

View File

@ -6,6 +6,7 @@ import AssistantSettingPopup from '@renderer/components/Popups/AssistantSettingP
import { isMac, isWindows } from '@renderer/config/constant' import { isMac, isWindows } from '@renderer/config/constant'
import { useTheme } from '@renderer/context/ThemeProvider' import { useTheme } from '@renderer/context/ThemeProvider'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore' import { useShowAssistants, useShowTopics } from '@renderer/hooks/useStore'
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
@ -27,8 +28,9 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
const { assistant, addTopic } = useAssistant(activeAssistant.id) const { assistant, addTopic } = useAssistant(activeAssistant.id)
const { t } = useTranslation() const { t } = useTranslation()
const { showAssistants, toggleShowAssistants } = useShowAssistants() const { showAssistants, toggleShowAssistants } = useShowAssistants()
const { showTopics } = useShowTopics() const { showTopics, toggleShowTopics } = useShowTopics()
const { theme, toggleTheme } = useTheme() const { theme, toggleTheme } = useTheme()
const { topicPosition } = useSettings()
const onCreateAssistant = async () => { const onCreateAssistant = async () => {
const assistant = await AddAssistantPopup.show() const assistant = await AddAssistantPopup.show()
@ -53,7 +55,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
</NewButton> </NewButton>
</NavbarLeft> </NavbarLeft>
)} )}
{showTopics && ( {showTopics && topicPosition === 'left' && (
<NavbarCenter <NavbarCenter
style={{ style={{
paddingLeft: isMac && !showAssistants ? 16 : 8, paddingLeft: isMac && !showAssistants ? 16 : 8,
@ -80,7 +82,7 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
)} )}
<NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 130 : 12, flex: 1 }}> <NavbarRight style={{ justifyContent: 'space-between', paddingRight: isWindows ? 130 : 12, flex: 1 }}>
<HStack alignItems="center"> <HStack alignItems="center">
{!showAssistants && !showTopics && ( {!showAssistants && (topicPosition === 'left' ? !showTopics : true) && (
<NewButton <NewButton
onClick={() => toggleShowAssistants()} onClick={() => toggleShowAssistants()}
style={{ marginRight: isMac ? 8 : 25, marginLeft: isMac ? 8 : 0 }}> style={{ marginRight: isMac ? 8 : 25, marginLeft: isMac ? 8 : 0 }}>
@ -102,6 +104,11 @@ const HeaderNavbar: FC<Props> = ({ activeAssistant, setActiveAssistant, setActiv
checked={theme === 'dark'} checked={theme === 'dark'}
onChange={toggleTheme} onChange={toggleTheme}
/> />
{topicPosition === 'right' && (
<NewButton onClick={toggleShowTopics}>
<i className={`iconfont icon-sidebar-${showTopics ? 'left' : 'right'}`} />
</NewButton>
)}
</HStack> </HStack>
</NavbarRight> </NavbarRight>
</Navbar> </Navbar>

View File

@ -1,5 +1,5 @@
import { BarsOutlined, SettingOutlined } from '@ant-design/icons' import { BarsOutlined, SettingOutlined } from '@ant-design/icons'
import { useShowRightSidebar } from '@renderer/hooks/useStore' import { useShowTopics } from '@renderer/hooks/useStore'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { Segmented } from 'antd' import { Segmented } from 'antd'
@ -18,7 +18,7 @@ interface Props {
const RightSidebar: FC<Props> = (props) => { const RightSidebar: FC<Props> = (props) => {
const [tab, setTab] = useState<'topic' | 'settings'>('topic') const [tab, setTab] = useState<'topic' | 'settings'>('topic')
const { rightSidebarShown, showRightSidebar, hideRightSidebar } = useShowRightSidebar() const { showTopics, setShowTopics } = useShowTopics()
const { t } = useTranslation() const { t } = useTranslation()
const isTopicTab = tab === 'topic' const isTopicTab = tab === 'topic'
const isSettingsTab = tab === 'settings' const isSettingsTab = tab === 'settings'
@ -26,31 +26,31 @@ const RightSidebar: FC<Props> = (props) => {
useEffect(() => { useEffect(() => {
const unsubscribes = [ const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => { EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => {
if (rightSidebarShown && isTopicTab) { if (showTopics && isTopicTab) {
return hideRightSidebar() return setShowTopics(false)
} }
if (rightSidebarShown) { if (showTopics) {
return setTab('topic') return setTab('topic')
} }
showRightSidebar() setShowTopics(true)
setTab('topic') setTab('topic')
}), }),
EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => { EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => {
if (rightSidebarShown && isSettingsTab) { if (showTopics && isSettingsTab) {
return hideRightSidebar() return setShowTopics(false)
} }
if (rightSidebarShown) { if (showTopics) {
return setTab('settings') return setTab('settings')
} }
showRightSidebar() setShowTopics(true)
setTab('settings') setTab('settings')
}), }),
EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => setTab('topic')) EventEmitter.on(EVENT_NAMES.SWITCH_TOPIC_SIDEBAR, () => setTab('topic'))
] ]
return () => unsubscribes.forEach((unsub) => unsub()) return () => unsubscribes.forEach((unsub) => unsub())
}, [hideRightSidebar, isSettingsTab, isTopicTab, rightSidebarShown, showRightSidebar]) }, [isSettingsTab, isTopicTab, showTopics, setShowTopics])
if (!rightSidebarShown) { if (!showTopics) {
return null return null
} }

View File

@ -2,6 +2,7 @@ import { DeleteOutlined, EditOutlined, OpenAIOutlined } from '@ant-design/icons'
import DragableList from '@renderer/components/DragableList' import DragableList from '@renderer/components/DragableList'
import PromptPopup from '@renderer/components/Popups/PromptPopup' import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings'
import { fetchMessagesSummary } from '@renderer/services/api' import { fetchMessagesSummary } from '@renderer/services/api'
import LocalStorage from '@renderer/services/storage' import LocalStorage from '@renderer/services/storage'
import { useAppSelector } from '@renderer/store' import { useAppSelector } from '@renderer/store'
@ -21,6 +22,9 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
const { assistant, removeTopic, updateTopic, updateTopics } = useAssistant(_assistant.id) const { assistant, removeTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
const { t } = useTranslation() const { t } = useTranslation()
const generating = useAppSelector((state) => state.runtime.generating) const generating = useAppSelector((state) => state.runtime.generating)
const { topicPosition } = useSettings()
const borderStyle = '0.5px solid var(--color-border)'
const getTopicMenuItems = useCallback( const getTopicMenuItems = useCallback(
(topic: Topic) => { (topic: Topic) => {
@ -88,17 +92,19 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
) )
return ( return (
<Container> <Container style={topicPosition === 'left' ? { borderRight: borderStyle } : { borderLeft: borderStyle }}>
<DragableList list={assistant.topics} onUpdate={updateTopics}> <DragableList list={assistant.topics} onUpdate={updateTopics}>
{(topic) => ( {(topic) => {
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}> const isActive = topic.id === activeTopic?.id
<TopicListItem const activeClass = topicPosition === 'left' ? 'active-left' : 'active-right'
className={topic.id === activeTopic?.id ? 'active' : ''} return (
onClick={() => onSwitchTopic(topic)}> <Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
{topic.name} <TopicListItem className={isActive ? activeClass : ''} onClick={() => onSwitchTopic(topic)}>
</TopicListItem> {topic.name}
</Dropdown> </TopicListItem>
)} </Dropdown>
)
}}
</DragableList> </DragableList>
</Container> </Container>
) )
@ -112,6 +118,7 @@ const Container = styled.div`
min-width: var(--topic-list-width); min-width: var(--topic-list-width);
max-width: var(--topic-list-width); max-width: var(--topic-list-width);
border-right: 0.5px solid var(--color-border); border-right: 0.5px solid var(--color-border);
border-left: 0.5px solid var(--color-border);
overflow-y: scroll; overflow-y: scroll;
height: calc(100vh - var(--navbar-height)); height: calc(100vh - var(--navbar-height));
` `
@ -129,11 +136,15 @@ const TopicListItem = styled.div`
&:hover { &:hover {
background-color: var(--color-background-soft); background-color: var(--color-background-soft);
} }
&.active { &.active-left {
background-color: var(--color-primary); background-color: var(--color-primary);
color: white; color: white;
font-weight: 500; font-weight: 500;
} }
&.active-right {
background-color: var(--color-background-mute);
font-weight: 500;
}
` `
export default Topics export default Topics

View File

@ -14,7 +14,17 @@ import { useTranslation } from 'react-i18next'
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '.' import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '.'
const GeneralSettings: FC = () => { const GeneralSettings: FC = () => {
const { language, proxyUrl: storeProxyUrl, userName, theme, windowStyle, setTheme, setWindowStyle } = useSettings() const {
language,
proxyUrl: storeProxyUrl,
userName,
theme,
windowStyle,
topicPosition,
setTheme,
setWindowStyle,
setTopicPosition
} = useSettings()
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl) const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { t } = useTranslation() const { t } = useTranslation()
@ -79,6 +89,19 @@ const GeneralSettings: FC = () => {
/> />
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.topic.position')}</SettingRowTitle>
<Select
defaultValue={topicPosition || 'right'}
style={{ width: 180 }}
onChange={setTopicPosition}
options={[
{ value: 'left', label: t('settings.topic.position.left') },
{ value: 'right', label: t('settings.topic.position.right') }
]}
/>
</SettingRow>
<SettingDivider />
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.general.user_name')}</SettingRowTitle> <SettingRowTitle>{t('settings.general.user_name')}</SettingRowTitle>
<Input <Input

View File

@ -18,6 +18,8 @@ export function getDefaultAssistant(): Assistant {
export function getDefaultTopic(): Topic { export function getDefaultTopic(): Topic {
return { return {
id: uuid(), id: uuid(),
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
name: i18n.t('chat.default.topic.name'), name: i18n.t('chat.default.topic.name'),
messages: [] messages: []
} }

View File

@ -393,6 +393,10 @@ const migrateConfig = {
updatedAt: new Date().toISOString() updatedAt: new Date().toISOString()
})) }))
})) }))
},
settings: {
...state.settings,
topicPosition: 'right'
} }
} }
} }

View File

@ -9,7 +9,6 @@ export enum ThemeMode {
} }
export interface SettingsState { export interface SettingsState {
showRightSidebar: boolean
showAssistants: boolean showAssistants: boolean
showTopics: boolean showTopics: boolean
sendMessageShortcut: SendMessageShortcut sendMessageShortcut: SendMessageShortcut
@ -22,10 +21,10 @@ export interface SettingsState {
theme: ThemeMode theme: ThemeMode
windowStyle: 'transparent' | 'opaque' windowStyle: 'transparent' | 'opaque'
fontSize: number fontSize: number
topicPosition: 'left' | 'right'
} }
const initialState: SettingsState = { const initialState: SettingsState = {
showRightSidebar: true,
showAssistants: true, showAssistants: true,
showTopics: true, showTopics: true,
sendMessageShortcut: 'Enter', sendMessageShortcut: 'Enter',
@ -37,19 +36,14 @@ const initialState: SettingsState = {
showInputEstimatedTokens: false, showInputEstimatedTokens: false,
theme: ThemeMode.light, theme: ThemeMode.light,
windowStyle: 'opaque', windowStyle: 'opaque',
fontSize: 14 fontSize: 14,
topicPosition: 'right'
} }
const settingsSlice = createSlice({ const settingsSlice = createSlice({
name: 'settings', name: 'settings',
initialState, initialState,
reducers: { reducers: {
toggleRightSidebar: (state) => {
state.showRightSidebar = !state.showRightSidebar
},
setShowRightSidebar: (state, action: PayloadAction<boolean>) => {
state.showRightSidebar = action.payload
},
setShowAssistants: (state, action: PayloadAction<boolean>) => { setShowAssistants: (state, action: PayloadAction<boolean>) => {
state.showAssistants = action.payload state.showAssistants = action.payload
}, },
@ -92,13 +86,14 @@ const settingsSlice = createSlice({
setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => { setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => {
state.windowStyle = action.payload state.windowStyle = action.payload
console.log(state.windowStyle) console.log(state.windowStyle)
},
setTopicPosition: (state, action: PayloadAction<'left' | 'right'>) => {
state.topicPosition = action.payload
} }
} }
}) })
export const { export const {
setShowRightSidebar,
toggleRightSidebar,
setShowAssistants, setShowAssistants,
toggleShowAssistants, toggleShowAssistants,
setShowTopics, setShowTopics,
@ -112,7 +107,8 @@ export const {
setShowInputEstimatedTokens, setShowInputEstimatedTokens,
setTheme, setTheme,
setFontSize, setFontSize,
setWindowStyle setWindowStyle,
setTopicPosition
} = settingsSlice.actions } = settingsSlice.actions
export default settingsSlice.reducer export default settingsSlice.reducer