feat: add setting panel

This commit is contained in:
kangfenmao 2024-07-24 16:03:02 +08:00
parent 9d96b826e2
commit d0280186bc
15 changed files with 256 additions and 233 deletions

View File

@ -27,6 +27,10 @@
--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-mute);
--color-primary: #00b96b;
--color-primary-soft: #00b96b99;
--color-primary-mute: #00b96b33;
--color-text: var(--color-text-1); --color-text: var(--color-text-1);
--color-icon: #ffffff99; --color-icon: #ffffff99;
--color-icon-white: #ffffff; --color-icon-white: #ffffff;
@ -36,8 +40,8 @@
--navbar-background: #1f1f1f; --navbar-background: #1f1f1f;
--navbar-height: 42px; --navbar-height: 42px;
--sidebar-width: 55px; --sidebar-width: 55px;
--assistants-width: 250px; --assistants-width: 245px;
--topic-list-width: 250px; --topic-list-width: 260px;
--settings-width: var(--assistants-width); --settings-width: var(--assistants-width);
--status-bar-height: 40px; --status-bar-height: 40px;
--input-bar-height: 125px; --input-bar-height: 125px;
@ -103,29 +107,3 @@ body,
#inputbar .ant-input { #inputbar .ant-input {
resize: none; resize: none;
} }
/* 全局初始化滚动条样式 */
::-webkit-scrollbar {
width: 5px;
height: 5px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
}
::-webkit-scrollbar-thumb:hover {
background: rgba(255, 255, 255, 0.4);
}
/* Safari 和 Chrome */
@media screen and (-webkit-min-device-pixel-ratio: 0) {
body {
scrollbar-width: thin; /* 告诉 FF 用细滚动条 */
scrollbar-color: rgba(0, 0, 0, 0.4) rgba(0, 0, 0, 0.1); /* FF 前面色后面色 */
}
}

View File

@ -0,0 +1,15 @@
/* 全局初始化滚动条样式 */
::-webkit-scrollbar {
width: 3px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.2);
&:hover {
background: rgba(255, 255, 255, 0.4);
}
}

View File

@ -5,6 +5,7 @@ import { setAvatar } from '@renderer/store/runtime'
import { runAsyncFunction } from '@renderer/utils' import { runAsyncFunction } from '@renderer/utils'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useSettings } from './useSettings' import { useSettings } from './useSettings'
import { isWindows } from '@renderer/config/constant'
export function useAppInit() { export function useAppInit() {
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
@ -28,4 +29,8 @@ export function useAppInit() {
useEffect(() => { useEffect(() => {
proxyUrl && window.api.setProxy(proxyUrl) proxyUrl && window.api.setProxy(proxyUrl)
}, [proxyUrl]) }, [proxyUrl])
useEffect(() => {
isWindows && import('@renderer/assets/styles/scrollbar.scss')
}, [])
} }

View File

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

View File

@ -51,8 +51,6 @@ const resources = {
'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',
'topics.hide_topics': 'Hide Topics',
'topics.show_topics': 'Show Topics',
'topics.auto_rename': 'Auto Rename', 'topics.auto_rename': 'Auto Rename',
'topics.edit.title': 'Rename', 'topics.edit.title': 'Rename',
'topics.edit.placeholder': 'Enter new name', 'topics.edit.placeholder': 'Enter new name',
@ -103,14 +101,15 @@ const resources = {
model: 'Model Settings', model: 'Model Settings',
assistant: 'Default Assistant', assistant: 'Default Assistant',
about: 'About & Feedback', about: 'About & Feedback',
'messages.model.title': 'Model Settings',
'messages.title': 'Message Settings',
'messages.divider': 'Show divider between messages',
'messages.use_serif_font': 'Use serif font',
'messages.input.title': 'Input Settings',
'messages.input.show_estimated_tokens': 'Show estimated input tokens',
'general.title': 'General Settings', 'general.title': 'General Settings',
'general.message.title': 'Message Settings',
'general.message.divider': 'Show divider between messages',
'general.message.use_serif_font': 'Use serif font',
'general.user_name': 'User Name', 'general.user_name': 'User Name',
'general.user_name.placeholder': 'Enter your name', 'general.user_name.placeholder': 'Enter your name',
'general.input.title': 'Input Settings',
'general.input.show_estimated_tokens': 'Show estimated input tokens',
'provider.api_key': 'API Key', 'provider.api_key': 'API Key',
'provider.check': 'Check', 'provider.check': 'Check',
'provider.get_api_key': 'Get API Key', 'provider.get_api_key': 'Get API Key',
@ -203,8 +202,6 @@ const resources = {
'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。', 'default.description': '你好,我是默认助手。你可以立刻开始跟我聊天。',
'default.topic.name': '默认话题', 'default.topic.name': '默认话题',
'topics.title': '话题', 'topics.title': '话题',
'topics.hide_topics': '隐藏话题',
'topics.show_topics': '显示话题',
'topics.auto_rename': 'AI 重命名', 'topics.auto_rename': 'AI 重命名',
'topics.edit.title': '重命名', 'topics.edit.title': '重命名',
'topics.edit.placeholder': '输入新名称', 'topics.edit.placeholder': '输入新名称',
@ -256,14 +253,15 @@ const resources = {
model: '模型设置', model: '模型设置',
assistant: '默认助手', assistant: '默认助手',
about: '关于我们', about: '关于我们',
'messages.model.title': '模型设置',
'messages.title': '消息设置',
'messages.divider': '消息分割线',
'messages.use_serif_font': '使用衬线字体',
'messages.input.title': '输入设置',
'messages.input.show_estimated_tokens': '状态显示',
'general.title': '常规设置', 'general.title': '常规设置',
'general.user_name': '用户名', 'general.user_name': '用户名',
'general.user_name.placeholder': '请输入用户名', 'general.user_name.placeholder': '请输入用户名',
'general.message.title': '消息设置',
'general.message.divider': '消息分割线',
'general.message.use_serif_font': '使用衬线字体',
'general.input.title': '输入设置',
'general.input.show_estimated_tokens': '状态显示',
'provider.api_key': 'API 密钥', 'provider.api_key': 'API 密钥',
'provider.check': '检查', 'provider.check': '检查',
'provider.get_api_key': '点击这里获取密钥', 'provider.get_api_key': '点击这里获取密钥',

View File

@ -6,18 +6,15 @@ import Chat from './components/Chat'
import Assistants from './components/Assistants' import Assistants from './components/Assistants'
import { uuid } from '@renderer/utils' import { uuid } from '@renderer/utils'
import { useShowAssistants, useShowRightSidebar } from '@renderer/hooks/useStore' import { useShowAssistants, useShowRightSidebar } from '@renderer/hooks/useStore'
import { Tooltip } from 'antd'
import Navigation from './components/NavigationCenter' import Navigation from './components/NavigationCenter'
import { useTranslation } from 'react-i18next'
import { isMac, isWindows } from '@renderer/config/constant' import { isMac, isWindows } from '@renderer/config/constant'
const HomePage: FC = () => { const HomePage: FC = () => {
const { assistants, addAssistant } = useAssistants() const { assistants, addAssistant } = useAssistants()
const [activeAssistant, setActiveAssistant] = useState(assistants[0]) const [activeAssistant, setActiveAssistant] = useState(assistants[0])
const { showRightSidebar, toggleRightSidebar } = useShowRightSidebar() const { rightSidebarShown, toggleRightSidebar } = useShowRightSidebar()
const { showAssistants, toggleShowAssistants } = useShowAssistants() const { showAssistants, toggleShowAssistants } = useShowAssistants()
const { defaultAssistant } = useDefaultAssistant() const { defaultAssistant } = useDefaultAssistant()
const { t } = useTranslation()
const onCreateAssistant = () => { const onCreateAssistant = () => {
const assistant = { ...defaultAssistant, id: uuid() } const assistant = { ...defaultAssistant, id: uuid() }
@ -40,14 +37,9 @@ const HomePage: FC = () => {
)} )}
<Navigation activeAssistant={activeAssistant} /> <Navigation activeAssistant={activeAssistant} />
<NavbarRight style={{ justifyContent: 'flex-end', paddingRight: isWindows ? 140 : 8 }}> <NavbarRight style={{ justifyContent: 'flex-end', paddingRight: isWindows ? 140 : 8 }}>
<Tooltip
placement="left"
title={showRightSidebar ? t('assistant.topics.hide_topics') : t('assistant.topics.show_topics')}
arrow>
<NewButton onClick={toggleRightSidebar}> <NewButton onClick={toggleRightSidebar}>
<i className={`iconfont ${showRightSidebar ? 'icon-showsidebarhoriz' : 'icon-hidesidebarhoriz'}`} /> <i className={`iconfont ${rightSidebarShown ? 'icon-showsidebarhoriz' : 'icon-hidesidebarhoriz'}`} />
</NewButton> </NewButton>
</Tooltip>
</NavbarRight> </NavbarRight>
</Navbar> </Navbar>
<ContentContainer> <ContentContainer>

View File

@ -4,7 +4,7 @@ import styled from 'styled-components'
import Inputbar from './Inputbar' import Inputbar from './Inputbar'
import Messages from './Messages' import Messages from './Messages'
import { Flex } from 'antd' import { Flex } from 'antd'
import Topics from './Topics' import RightSidebar from './RightSidebar'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useActiveTopic } from '@renderer/hooks/useTopic' import { useActiveTopic } from '@renderer/hooks/useTopic'
@ -22,7 +22,7 @@ const Chat: FC<Props> = (props) => {
<Messages assistant={assistant} topic={activeTopic} /> <Messages assistant={assistant} topic={activeTopic} />
<Inputbar assistant={assistant} setActiveTopic={setActiveTopic} /> <Inputbar assistant={assistant} setActiveTopic={setActiveTopic} />
</Flex> </Flex>
<Topics assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} /> <RightSidebar assistant={assistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
</Container> </Container>
) )
} }

View File

@ -10,7 +10,6 @@ import {
} from '@ant-design/icons' } from '@ant-design/icons'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { getDefaultTopic } from '@renderer/services/assistant' import { getDefaultTopic } from '@renderer/services/assistant'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event' import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import store, { useAppSelector } from '@renderer/store' import store, { useAppSelector } from '@renderer/store'
@ -24,7 +23,6 @@ import { debounce, isEmpty } from 'lodash'
import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react' import { FC, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import styled from 'styled-components'
import AssistantSettings from './AssistantSettings'
import SendMessageSetting from './SendMessageSetting' import SendMessageSetting from './SendMessageSetting'
import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant' import { DEFAULT_CONEXTCOUNT } from '@renderer/config/constant'
@ -35,7 +33,6 @@ interface Props {
const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => { const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
const [text, setText] = useState('') const [text, setText] = useState('')
const { toggleRightSidebar } = useShowRightSidebar()
const { addTopic } = useAssistant(assistant.id) const { addTopic } = useAssistant(assistant.id)
const { sendMessageShortcut, showInputEstimatedTokens } = useSettings() const { sendMessageShortcut, showInputEstimatedTokens } = useSettings()
const [expended, setExpend] = useState(false) const [expended, setExpend] = useState(false)
@ -104,6 +101,7 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
if (!generating) { if (!generating) {
if ((e.ctrlKey || e.metaKey) && e.key === 'n') { if ((e.ctrlKey || e.metaKey) && e.key === 'n') {
addNewTopic() addNewTopic()
EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)
inputRef.current?.focus() inputRef.current?.focus()
} }
} }
@ -137,11 +135,6 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
<PlusCircleOutlined /> <PlusCircleOutlined />
</ToolbarButton> </ToolbarButton>
</Tooltip> </Tooltip>
<Tooltip placement="top" title={t('assistant.input.topics')} arrow>
<ToolbarButton type="text" onClick={toggleRightSidebar}>
<HistoryOutlined />
</ToolbarButton>
</Tooltip>
<Tooltip placement="top" title={t('assistant.input.clear')} arrow> <Tooltip placement="top" title={t('assistant.input.clear')} arrow>
<Popconfirm <Popconfirm
icon={false} icon={false}
@ -155,11 +148,16 @@ const Inputbar: FC<Props> = ({ assistant, setActiveTopic }) => {
</ToolbarButton> </ToolbarButton>
</Popconfirm> </Popconfirm>
</Tooltip> </Tooltip>
<AssistantSettings assistant={assistant}> <Tooltip placement="top" title={t('assistant.input.topics')} arrow>
<ToolbarButton type="text"> <ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_TOPIC_SIDEBAR)}>
<HistoryOutlined />
</ToolbarButton>
</Tooltip>
<Tooltip placement="top" title={t('assistant.input.settings')} arrow>
<ToolbarButton type="text" onClick={() => EventEmitter.emit(EVENT_NAMES.SHOW_CHAT_SETTINGS)}>
<ControlOutlined /> <ControlOutlined />
</ToolbarButton> </ToolbarButton>
</AssistantSettings> </Tooltip>
<Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow> <Tooltip placement="top" title={expended ? t('assistant.input.collapse') : t('assistant.input.expand')} arrow>
<ToolbarButton type="text" onClick={() => setExpend(!expended)}> <ToolbarButton type="text" onClick={() => setExpend(!expended)}>
{expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />} {expended ? <FullscreenExitOutlined /> : <FullscreenOutlined />}

View File

@ -0,0 +1,100 @@
import { Assistant, Topic } from '@renderer/types'
import { FC, useEffect, useState } from 'react'
import styled from 'styled-components'
import TopicsTab from './TopicsTab'
import SettingsTab from './SettingsTab'
import { useTranslation } from 'react-i18next'
import { EVENT_NAMES, EventEmitter } from '@renderer/services/event'
import { useShowRightSidebar } from '@renderer/hooks/useStore'
interface Props {
assistant: Assistant
activeTopic: Topic
setActiveTopic: (topic: Topic) => void
}
const RightSidebar: FC<Props> = (props) => {
const [tab, setTab] = useState<'topic' | 'settings'>('topic')
const { rightSidebarShown, showRightSidebar, hideRightSidebar } = useShowRightSidebar()
const { t } = useTranslation()
const isTopicTab = tab === 'topic'
const isSettingsTab = tab === 'settings'
useEffect(() => {
const unsubscribes = [
EventEmitter.on(EVENT_NAMES.SHOW_TOPIC_SIDEBAR, (): any => {
if (rightSidebarShown && isTopicTab) {
return hideRightSidebar()
}
if (rightSidebarShown) {
return setTab('topic')
}
showRightSidebar()
setTab('topic')
}),
EventEmitter.on(EVENT_NAMES.SHOW_CHAT_SETTINGS, (): any => {
if (rightSidebarShown && isSettingsTab) {
return hideRightSidebar()
}
if (rightSidebarShown) {
return setTab('settings')
}
showRightSidebar()
setTab('settings')
})
]
return () => unsubscribes.forEach((unsub) => unsub())
}, [hideRightSidebar, isSettingsTab, isTopicTab, rightSidebarShown, showRightSidebar])
return (
<Container style={{ display: rightSidebarShown ? 'block' : 'none' }}>
<Tabs>
<Tab className={tab === 'topic' ? 'active' : ''} onClick={() => setTab('topic')}>
{t('common.topics')}
</Tab>
<Tab className={tab === 'settings' ? 'active' : ''} onClick={() => setTab('settings')}>
{t('settings.title')}
</Tab>
</Tabs>
{tab === 'topic' && <TopicsTab {...props} />}
{tab === 'settings' && <SettingsTab assistant={props.assistant} />}
</Container>
)
}
const Container = styled.div`
width: var(--topic-list-width);
height: 100%;
border-left: 0.5px solid var(--color-border);
overflow-y: auto;
.collapsed {
width: 0;
border-left: none;
}
`
const Tabs = styled.div`
display: flex;
flex-direction: row;
border-bottom: 1px solid var(--color-border);
padding: 0 10px;
`
const Tab = styled.div`
padding: 8px 0;
font-weight: 500;
display: flex;
flex: 1;
justify-content: center;
align-items: center;
font-size: 13px;
cursor: pointer;
color: #8a8a8a;
border-bottom: 1px solid transparent;
&.active {
color: white;
font-weight: 600;
}
`
export default RightSidebar

View File

@ -1,23 +1,31 @@
import { QuestionCircleOutlined } from '@ant-design/icons' import { Assistant } from '@renderer/types'
import styled from 'styled-components'
import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant' import { DEFAULT_CONEXTCOUNT, DEFAULT_TEMPERATURE } from '@renderer/config/constant'
import { useAssistant } from '@renderer/hooks/useAssistant' import { useAssistant } from '@renderer/hooks/useAssistant'
import { Assistant } from '@renderer/types' import { Button, Col, InputNumber, Row, Slider, Switch, Tooltip } from 'antd'
import { Button, Col, InputNumber, Popover, Row, Slider, Tooltip } from 'antd'
import { debounce } from 'lodash' import { debounce } from 'lodash'
import { FC, PropsWithChildren, useCallback, useEffect, useState } from 'react' import { FC, useCallback, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import styled from 'styled-components' import { QuestionCircleOutlined } from '@ant-design/icons'
import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@renderer/pages/settings/components'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { setMessageFont, setShowInputEstimatedTokens, setShowMessageDivider } from '@renderer/store/settings'
interface Props { interface Props {
assistant: Assistant assistant: Assistant
} }
const PopoverContent: FC<Props> = (props) => { const SettingsTab: FC<Props> = (props) => {
const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id) const { assistant, updateAssistantSettings, updateAssistant } = useAssistant(props.assistant.id)
const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE) const [temperature, setTemperature] = useState(assistant?.settings?.temperature ?? DEFAULT_TEMPERATURE)
const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT) const [contextCount, setConextCount] = useState(assistant?.settings?.contextCount ?? DEFAULT_CONEXTCOUNT)
const { t } = useTranslation() const { t } = useTranslation()
const dispatch = useAppDispatch()
const { showMessageDivider, messageFont, showInputEstimatedTokens } = useSettings()
const onUpdateAssistantSettings = useCallback( const onUpdateAssistantSettings = useCallback(
debounce( debounce(
({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => { ({ _temperature, _contextCount }: { _temperature?: number; _contextCount?: number }) => {
@ -70,30 +78,29 @@ const PopoverContent: FC<Props> = (props) => {
return ( return (
<Container> <Container>
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}> <SettingSubtitle>{t('settings.messages.model.title')}</SettingSubtitle>
<Col span={6}> <SettingDivider />
<Row align="middle" justify="end"> <Row align="middle">
<Label>{t('assistant.settings.temperature')}</Label> <Label>{t('assistant.settings.conext_count')}</Label>
<Tooltip title={t('assistant.settings.temperature.tip')}> <Tooltip title={t('assistant.settings.temperature.tip')}>
<QuestionIcon /> <QuestionIcon />
</Tooltip> </Tooltip>
</Row> </Row>
</Col> <Row align="middle" gutter={10}>
<Col span={14}> <Col span={18}>
<Slider <Slider
min={0} min={0}
max={1.2} max={1.2}
onChange={onTemperatureChange} onChange={onTemperatureChange}
value={typeof temperature === 'number' ? temperature : 0} value={typeof temperature === 'number' ? temperature : 0}
marks={{ 0: '0', 0.7: '0.7', 1: '1', 1.2: '1.2' }} marks={{ 0: '0', 0.7: '0.7', 1.2: '1.2' }}
step={0.1} step={0.1}
/> />
</Col> </Col>
<Col span={3}> <Col span={6}>
<InputNumber <InputNumberic
min={0} min={0}
max={1.2} max={1.2}
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
step={0.1} step={0.1}
value={temperature} value={temperature}
onChange={onTemperatureChange} onChange={onTemperatureChange}
@ -101,16 +108,14 @@ const PopoverContent: FC<Props> = (props) => {
/> />
</Col> </Col>
</Row> </Row>
<Row align="middle" style={{ marginBottom: 10 }} gutter={20}> <Row align="middle">
<Col span={6}>
<Row align="middle" justify="end">
<Label>{t('assistant.settings.conext_count')}</Label> <Label>{t('assistant.settings.conext_count')}</Label>
<Tooltip title={t('assistant.settings.conext_count.tip')}> <Tooltip title={t('assistant.settings.conext_count.tip')}>
<QuestionIcon /> <QuestionIcon />
</Tooltip> </Tooltip>
</Row> </Row>
</Col> <Row align="middle" gutter={10}>
<Col span={14}> <Col span={18}>
<Slider <Slider
min={0} min={0}
max={20} max={20}
@ -120,11 +125,10 @@ const PopoverContent: FC<Props> = (props) => {
step={1} step={1}
/> />
</Col> </Col>
<Col span={3}> <Col span={6}>
<InputNumber <InputNumberic
min={0} min={0}
max={20} max={20}
style={{ width: 50, marginLeft: 5, textAlign: 'center' }}
step={1} step={1}
value={contextCount} value={contextCount}
onChange={onConextCountChange} onChange={onConextCountChange}
@ -132,49 +136,63 @@ const PopoverContent: FC<Props> = (props) => {
/> />
</Col> </Col>
</Row> </Row>
<Row justify="center"> <Button onClick={onReset} style={{ width: '100%' }}>
<Button onClick={onReset}>{t('assistant.settings.reset')}</Button> {t('assistant.settings.reset')}
</Row> </Button>
<SettingSubtitle>{t('settings.messages.title')}</SettingSubtitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.messages.divider')}</SettingRowTitle>
<Switch checked={showMessageDivider} onChange={(checked) => dispatch(setShowMessageDivider(checked))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.messages.use_serif_font')}</SettingRowTitle>
<Switch
checked={messageFont === 'serif'}
onChange={(checked) => dispatch(setMessageFont(checked ? 'serif' : 'system'))}
/>
</SettingRow>
<SettingDivider />
<SettingSubtitle style={{ marginTop: 20 }}>{t('settings.messages.input.title')}</SettingSubtitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitle>
<Switch
checked={showInputEstimatedTokens}
onChange={(checked) => dispatch(setShowInputEstimatedTokens(checked))}
/>
</SettingRow>
<SettingDivider />
</Container> </Container>
) )
} }
const AssistantSettings: FC<Props & PropsWithChildren> = ({ children, assistant }) => {
const [open, setOpen] = useState(false)
const { t } = useTranslation()
return (
<Popover content={<PopoverContent assistant={assistant} />} trigger="click" onOpenChange={setOpen}>
{open ? (
children
) : (
<Tooltip placement="top" title={t('assistant.input.settings')} arrow>
{children}
</Tooltip>
)}
</Popover>
)
}
const Container = styled.div` const Container = styled.div`
display: flex; padding: 0 10px;
flex-direction: column; `
margin-bottom: 8px;
width: 420px; const InputNumberic = styled(InputNumber)`
padding: 5px; width: 45px;
padding: 0;
margin-left: 5px;
text-align: center;
.ant-input-number-input {
text-align: center;
}
` `
const Label = styled.p` const Label = styled.p`
margin: 0; margin: 0;
font-size: 14px; font-size: 12px;
font-weight: bold; font-weight: bold;
margin-right: 5px; margin-right: 8px;
` `
const QuestionIcon = styled(QuestionCircleOutlined)` const QuestionIcon = styled(QuestionCircleOutlined)`
font-size: 14px; font-size: 12px;
cursor: pointer; cursor: pointer;
color: var(--color-text-3); color: var(--color-text-3);
` `
export default AssistantSettings export default SettingsTab

View File

@ -3,7 +3,7 @@ import { useAssistant } from '@renderer/hooks/useAssistant'
import { useShowRightSidebar } from '@renderer/hooks/useStore' import { useShowRightSidebar } from '@renderer/hooks/useStore'
import { fetchMessagesSummary } from '@renderer/services/api' import { fetchMessagesSummary } from '@renderer/services/api'
import { Assistant, Topic } from '@renderer/types' import { Assistant, Topic } from '@renderer/types'
import { Button, Dropdown, MenuProps, Popconfirm } from 'antd' import { Dropdown, MenuProps } from 'antd'
import { FC } from 'react' import { FC } from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons' import { DeleteOutlined, EditOutlined, SignatureOutlined } from '@ant-design/icons'
@ -19,9 +19,9 @@ interface Props {
setActiveTopic: (topic: Topic) => void setActiveTopic: (topic: Topic) => void
} }
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => { const TopicsTab: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
const { showRightSidebar } = useShowRightSidebar() const { rightSidebarShown } = useShowRightSidebar()
const { assistant, removeTopic, updateTopic, removeAllTopics, 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)
@ -93,23 +93,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
} }
return ( return (
<Container style={{ display: showRightSidebar ? 'block' : 'none' }}> <Container style={{ display: rightSidebarShown ? 'block' : 'none' }}>
<TopicTitle>
<span>
{t('assistant.topics.title')} ({assistant.topics.length})
</span>
<Popconfirm
icon={false}
title={t('assistant.topics.delete.all.title')}
description={t('assistant.topics.delete.all.content')}
placement="leftBottom"
onConfirm={removeAllTopics}
okType="danger">
<DeleteButton type="text">
<DeleteIcon />
</DeleteButton>
</Popconfirm>
</TopicTitle>
<DragDropContext onDragEnd={onDragEnd}> <DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable"> <Droppable droppableId="droppable">
{(provided) => ( {(provided) => (
@ -138,15 +122,7 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
} }
const Container = styled.div` const Container = styled.div`
width: var(--topic-list-width); padding: 15px 10px;
height: 100%;
border-left: 0.5px solid var(--color-border);
padding: 10px;
overflow-y: auto;
&.collapsed {
width: 0;
border-left: none;
}
` `
const TopicListItem = styled.div` const TopicListItem = styled.div`
@ -166,31 +142,4 @@ const TopicListItem = styled.div`
} }
` `
const TopicTitle = styled.div` export default TopicsTab
font-weight: bold;
margin-bottom: 10px;
font-size: 14px;
color: var(--color-text-1);
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
`
const DeleteButton = styled(Button)`
width: 30px;
height: 30px;
border-radius: 50%;
padding: 0;
&:hover {
.anticon {
color: #ff4d4f;
}
}
`
const DeleteIcon = styled(DeleteOutlined)`
font-size: 16px;
`
export default Topics

View File

@ -1,6 +1,6 @@
import { FC, useState } from 'react' import { FC, useState } from 'react'
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components' import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from './components'
import { Avatar, Input, Select, Switch, Upload } from 'antd' import { Avatar, Input, Select, Upload } from 'antd'
import styled from 'styled-components' import styled from 'styled-components'
import LocalStorage from '@renderer/services/storage' import LocalStorage from '@renderer/services/storage'
import { compressImage, isValidProxyUrl } from '@renderer/utils' import { compressImage, isValidProxyUrl } from '@renderer/utils'
@ -8,27 +8,14 @@ import useAvatar from '@renderer/hooks/useAvatar'
import { useAppDispatch } from '@renderer/store' import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime' import { setAvatar } from '@renderer/store/runtime'
import { useSettings } from '@renderer/hooks/useSettings' import { useSettings } from '@renderer/hooks/useSettings'
import { import { setLanguage, setUserName } from '@renderer/store/settings'
setLanguage,
setMessageFont,
setShowInputEstimatedTokens,
setShowMessageDivider,
setUserName
} from '@renderer/store/settings'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings' import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
const GeneralSettings: FC = () => { const GeneralSettings: FC = () => {
const avatar = useAvatar() const avatar = useAvatar()
const { const { language, proxyUrl: storeProxyUrl, userName } = useSettings()
language,
proxyUrl: storeProxyUrl,
userName,
showMessageDivider,
messageFont,
showInputEstimatedTokens
} = 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()
@ -110,31 +97,6 @@ const GeneralSettings: FC = () => {
/> />
</SettingRow> </SettingRow>
<SettingDivider /> <SettingDivider />
<SettingTitle style={{ marginTop: 20 }}>{t('settings.general.message.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.message.divider')}</SettingRowTitle>
<Switch checked={showMessageDivider} onChange={(checked) => dispatch(setShowMessageDivider(checked))} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.message.use_serif_font')}</SettingRowTitle>
<Switch
checked={messageFont === 'serif'}
onChange={(checked) => dispatch(setMessageFont(checked ? 'serif' : 'system'))}
/>
</SettingRow>
<SettingDivider />
<SettingTitle style={{ marginTop: 20 }}>{t('settings.general.input.title')}</SettingTitle>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.input.show_estimated_tokens')}</SettingRowTitle>
<Switch
checked={showInputEstimatedTokens}
onChange={(checked) => dispatch(setShowInputEstimatedTokens(checked))}
/>
</SettingRow>
<SettingDivider />
</SettingContainer> </SettingContainer>
) )
} }

View File

@ -39,10 +39,10 @@ export const SettingRow = styled.div`
flex-direction: row; flex-direction: row;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
min-height: 40px;
` `
export const SettingRowTitle = styled.div` export const SettingRowTitle = styled.div`
font-size: 14px; font-size: 14px;
line-height: 18px;
color: var(--color-text-1); color: var(--color-text-1);
` `

View File

@ -11,5 +11,7 @@ export const EVENT_NAMES = {
EDIT_MESSAGE: 'EDIT_MESSAGE', EDIT_MESSAGE: 'EDIT_MESSAGE',
REGENERATE_MESSAGE: 'REGENERATE_MESSAGE', REGENERATE_MESSAGE: 'REGENERATE_MESSAGE',
CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED', CHAT_COMPLETION_PAUSED: 'CHAT_COMPLETION_PAUSED',
ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT' ESTIMATED_TOKEN_COUNT: 'ESTIMATED_TOKEN_COUNT',
SHOW_CHAT_SETTINGS: 'SHOW_CHAT_SETTINGS',
SHOW_TOPIC_SIDEBAR: 'SHOW_TOPIC_SIDEBAR'
} }

View File

@ -33,6 +33,9 @@ const settingsSlice = createSlice({
toggleRightSidebar: (state) => { toggleRightSidebar: (state) => {
state.showRightSidebar = !state.showRightSidebar state.showRightSidebar = !state.showRightSidebar
}, },
setShowRightSidebar: (state, action: PayloadAction<boolean>) => {
state.showRightSidebar = action.payload
},
toggleShowAssistants: (state) => { toggleShowAssistants: (state) => {
state.showAssistants = !state.showAssistants state.showAssistants = !state.showAssistants
}, },
@ -61,6 +64,7 @@ const settingsSlice = createSlice({
}) })
export const { export const {
setShowRightSidebar,
toggleRightSidebar, toggleRightSidebar,
toggleShowAssistants, toggleShowAssistants,
setSendMessageShortcut, setSendMessageShortcut,