feat: transparent window settings

This commit is contained in:
kangfenmao 2024-09-04 10:26:00 +08:00
parent 9e2c7a08df
commit 14acd45927
11 changed files with 100 additions and 96 deletions

View File

@ -48,8 +48,8 @@
--status-bar-height: 40px;
--input-bar-height: 85px;
--assistants-width: 300px;
--topic-list-width: 300px;
--assistants-width: 280px;
--topic-list-width: 280px;
--settings-width: var(--assistants-width);
}

View File

@ -70,8 +70,8 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
open={open}
onCancel={onCancel}
afterClose={onClose}
transitionName=""
maskTransitionName=""
transitionName="ant-move-down"
maskTransitionName="ant-fade"
footer={null}>
<Input
placeholder={t('common.search')}

View File

@ -1,15 +1,18 @@
import { isMac } from '@renderer/config/constant'
import { useSettings } from '@renderer/hooks/useSettings'
import { useRuntime } from '@renderer/hooks/useStore'
import { FC, PropsWithChildren } from 'react'
import styled from 'styled-components'
type Props = PropsWithChildren & JSX.IntrinsicElements['div']
const navbarBackgroundColor = isMac ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
export const Navbar: FC<Props> = ({ children, ...props }) => {
const { minappShow } = useRuntime()
const backgroundColor = minappShow ? 'var(--navbar-background)' : navbarBackgroundColor
const { windowStyle } = useSettings()
const macTransparentWindow = isMac && windowStyle === 'transparent'
const navbarBgColor = macTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
const backgroundColor = minappShow ? 'var(--navbar-background)' : navbarBgColor
return (
<NavbarContainer {...props} style={{ backgroundColor }}>
@ -39,7 +42,6 @@ const NavbarContainer = styled.div`
margin-left: ${isMac ? 'calc(var(--sidebar-width) * -1)' : 0};
padding-left: ${isMac ? 'var(--sidebar-width)' : 0};
border-bottom: 0.5px solid var(--color-border);
background-color: ${navbarBackgroundColor};
transition: background-color 0.3s ease;
-webkit-app-region: drag;
`

View File

@ -2,6 +2,7 @@ import { TranslationOutlined } from '@ant-design/icons'
import { isMac } from '@renderer/config/constant'
import { isLocalAi, UserAvatar } from '@renderer/config/env'
import useAvatar from '@renderer/hooks/useAvatar'
import { useSettings } from '@renderer/hooks/useSettings'
import { useRuntime, useShowAssistants } from '@renderer/hooks/useStore'
import { Avatar } from 'antd'
import { FC } from 'react'
@ -11,8 +12,6 @@ import styled from 'styled-components'
import UserPopup from '../Popups/UserPopup'
const sidebarBackgroundColor = isMac ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
const Sidebar: FC = () => {
const { pathname } = useLocation()
const avatar = useAvatar()
@ -21,11 +20,15 @@ const Sidebar: FC = () => {
const { generating } = useRuntime()
const { t } = useTranslation()
const navigate = useNavigate()
const { windowStyle } = useSettings()
const isRoute = (path: string): string => (pathname === path ? 'active' : '')
const onEditUser = () => UserPopup.show()
const macTransparentWindow = isMac && windowStyle === 'transparent'
const sidebarBgColor = macTransparentWindow ? 'var(--navbar-background-mac)' : 'var(--navbar-background)'
const to = (path: string) => {
if (generating) {
window.message.warning({ content: t('message.switch.disabled'), key: 'switch-assistant' })
@ -39,7 +42,7 @@ const Sidebar: FC = () => {
}
return (
<Container style={{ backgroundColor: minappShow ? 'var(--navbar-background)' : sidebarBackgroundColor }}>
<Container style={{ backgroundColor: minappShow ? 'var(--navbar-background)' : sidebarBgColor }}>
<AvatarImg src={avatar || UserAvatar} draggable={false} className="nodrag" onClick={onEditUser} />
<MainMenus>
<Menus>
@ -87,7 +90,6 @@ const Container = styled.div`
-webkit-app-region: drag !important;
border-right: 0.5px solid var(--color-border);
margin-top: ${isMac ? 'var(--navbar-height)' : 0};
background-color: ${sidebarBackgroundColor};
transition: background-color 0.3s ease;
`

View File

@ -3,6 +3,7 @@ import {
SendMessageShortcut,
setSendMessageShortcut as _setSendMessageShortcut,
setTheme,
setWindowStyle,
ThemeMode
} from '@renderer/store/settings'
@ -17,6 +18,9 @@ export function useSettings() {
},
setTheme(theme: ThemeMode) {
dispatch(setTheme(theme))
},
setWindowStyle(windowStyle: 'transparent' | 'opaque') {
dispatch(setWindowStyle(windowStyle))
}
}
}

View File

@ -202,6 +202,9 @@ const resources = {
'theme.dark': 'Dark',
'theme.light': 'Light',
'theme.auto': 'Auto',
'theme.window.style.title': 'Window Style',
'theme.window.style.transparent': 'Transparent Window',
'theme.window.style.opaque': 'Opaque Window',
'font_size.title': 'Message Font Size'
},
translate: {
@ -444,6 +447,9 @@ const resources = {
'theme.dark': '深色主题',
'theme.light': '浅色主题',
'theme.auto': '跟随系统',
'theme.window.style.title': '窗口样式',
'theme.window.style.transparent': '透明窗口',
'theme.window.style.opaque': '不透明窗口',
'font_size.title': '消息字体大小'
},
translate: {

View File

@ -106,7 +106,7 @@ const Assistants: FC<Props> = ({
if (showTopics) {
return (
<Container>
<Container style={{ padding: 0 }}>
<Topics assistant={activeAssistant} activeTopic={activeTopic} setActiveTopic={setActiveTopic} />
</Container>
)
@ -155,6 +155,7 @@ const AssistantItem = styled.div`
position: relative;
border-radius: 4px;
margin: 0 10px;
padding-right: 35px;
cursor: pointer;
font-family: Ubuntu;
.anticon {

View File

@ -64,7 +64,7 @@ const HomePage: FC = () => {
{showAssistants && (
<NavbarLeft
style={{ justifyContent: 'space-between', alignItems: 'center', borderRight: 'none', padding: '0 8px' }}>
<NavigtaionBack onClick={() => setShowTopics(false)}>
<NavigtaionBack className={showTopics ? 'back' : ''} onClick={() => setShowTopics(false)}>
{showTopics && <ArrowLeftOutlined />}
<NavigationBackTitle>{showTopics ? t('common.back') : t('common.chat')}</NavigationBackTitle>
</NavigtaionBack>
@ -124,17 +124,19 @@ const NavigtaionBack = styled.div`
align-items: center;
justify-content: flex-start;
gap: 10px;
cursor: pointer;
margin-left: ${isMac ? '14px' : '2px'};
margin-left: ${isMac ? '10px' : 0};
-webkit-app-region: none;
transition: all 0.2s ease-in-out;
transition: opacity 0.2s ease-in-out;
padding: 2px 8px;
padding: 3px 8px;
border-radius: 6px;
&.back {
cursor: pointer;
&:hover {
background-color: var(--color-background-mute);
color: var(--color-text-1);
}
}
`
const NavigationBackTitle = styled.div`
@ -172,7 +174,7 @@ export const NewButton = styled.div`
font-size: 17px;
}
&:hover {
background-color: var(--color-background-soft);
background-color: var(--color-background-mute);
cursor: pointer;
color: var(--color-icon-white);
}

View File

@ -1,13 +1,12 @@
import { DeleteOutlined, EditOutlined, OpenAIOutlined } from '@ant-design/icons'
import { DragDropContext, Draggable, Droppable, DropResult } from '@hello-pangea/dnd'
import DragableList from '@renderer/components/DragableList'
import PromptPopup from '@renderer/components/Popups/PromptPopup'
import { useAssistant } from '@renderer/hooks/useAssistant'
import { fetchMessagesSummary } from '@renderer/services/api'
import LocalStorage from '@renderer/services/storage'
import { useAppSelector } from '@renderer/store'
import { Assistant, Topic } from '@renderer/types'
import { droppableReorder } from '@renderer/utils'
import { Dropdown, MenuProps } from 'antd'
import { Button, Dropdown, MenuProps } from 'antd'
import { FC, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -19,7 +18,7 @@ interface Props {
}
const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic }) => {
const { assistant, removeTopic, updateTopic, updateTopics } = useAssistant(_assistant.id)
const { assistant, removeTopic, updateTopic, updateTopics, removeAllTopics } = useAssistant(_assistant.id)
const { t } = useTranslation()
const generating = useAppSelector((state) => state.runtime.generating)
@ -77,17 +76,6 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
[assistant, removeTopic, setActiveTopic, t, updateTopic]
)
const onDragEnd = useCallback(
(result: DropResult) => {
if (result.destination) {
const sourceIndex = result.source.index
const destIndex = result.destination.index
updateTopics(droppableReorder(assistant.topics, sourceIndex, destIndex))
}
},
[assistant.topics, updateTopics]
)
const onSwitchTopic = useCallback(
(topic: Topic) => {
if (generating) {
@ -99,20 +87,19 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
[generating, setActiveTopic, t]
)
const onDeleteAll = () => {
window.modal.confirm({
title: t('chat.topics.delete.all.title'),
content: t('chat.topics.delete.all.content'),
okButtonProps: { danger: true },
onOk: removeAllTopics
})
}
return (
<Container>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable">
{(provided) => (
<div {...provided.droppableProps} ref={provided.innerRef}>
{assistant.topics.map((topic, index) => (
<Draggable key={`draggable_${topic.id}_${index}`} draggableId={topic.id} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={{ ...provided.draggableProps.style, marginBottom: 5 }}>
<DragableList list={assistant.topics} onUpdate={updateTopics}>
{(topic) => (
<Dropdown menu={{ items: getTopicMenuItems(topic) }} trigger={['contextMenu']} key={topic.id}>
<TopicListItem
className={topic.id === activeTopic?.id ? 'active' : ''}
@ -120,14 +107,15 @@ const Topics: FC<Props> = ({ assistant: _assistant, activeTopic, setActiveTopic
{topic.name}
</TopicListItem>
</Dropdown>
</div>
)}
</Draggable>
))}
</div>
</DragableList>
{assistant.topics.length > 20 && (
<Footer>
<Button style={{ width: '100%' }} onClick={onDeleteAll}>
{t('chat.topics.delete.all.title')}
</Button>
</Footer>
)}
</Droppable>
</DragDropContext>
</Container>
)
}
@ -136,14 +124,11 @@ const Container = styled.div`
display: flex;
flex: 1;
flex-direction: column;
overflow-y: scroll;
padding-top: 10px;
padding-bottom: 10px;
margin-top: -10px;
`
const TopicListItem = styled.div`
padding: 7px 10px;
padding: 6px 10px;
margin: 0 10px;
cursor: pointer;
border-radius: 4px;
@ -151,6 +136,7 @@ const TopicListItem = styled.div`
overflow: hidden;
text-overflow: ellipsis;
font-family: Ubuntu;
font-size: 13px;
&:hover {
background-color: var(--color-background-soft);
}
@ -160,4 +146,10 @@ const TopicListItem = styled.div`
}
`
const Footer = styled.div`
padding: 0 10px;
padding-bottom: 10px;
width: 100%;
`
export default Topics

View File

@ -1,16 +1,13 @@
import { FolderOpenOutlined, SaveOutlined } from '@ant-design/icons'
import { HStack } from '@renderer/components/Layout'
import useAvatar from '@renderer/hooks/useAvatar'
import { useSettings } from '@renderer/hooks/useSettings'
import i18n from '@renderer/i18n'
import { backup, reset, restore } from '@renderer/services/backup'
import LocalStorage from '@renderer/services/storage'
import { useAppDispatch } from '@renderer/store'
import { setAvatar } from '@renderer/store/runtime'
import { setLanguage, setUserName, ThemeMode } from '@renderer/store/settings'
import { setProxyUrl as _setProxyUrl } from '@renderer/store/settings'
import { compressImage, isValidProxyUrl } from '@renderer/utils'
import { Avatar, Button, Input, Select, Upload } from 'antd'
import { isValidProxyUrl } from '@renderer/utils'
import { Avatar, Button, Input, Select } from 'antd'
import { FC, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
@ -18,8 +15,7 @@ import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingRow, SettingRowTitle, SettingTitle } from '.'
const GeneralSettings: FC = () => {
const avatar = useAvatar()
const { language, proxyUrl: storeProxyUrl, userName, theme, setTheme } = useSettings()
const { language, proxyUrl: storeProxyUrl, userName, theme, windowStyle, setTheme, setWindowStyle } = useSettings()
const [proxyUrl, setProxyUrl] = useState<string | undefined>(storeProxyUrl)
const dispatch = useAppDispatch()
const { t } = useTranslation()
@ -72,24 +68,16 @@ const GeneralSettings: FC = () => {
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('common.avatar')}</SettingRowTitle>
<Upload
customRequest={() => {}}
accept="image/png, image/jpeg"
itemRender={() => null}
maxCount={1}
onChange={async ({ file }) => {
try {
const _file = file.originFileObj as File
const compressedFile = await compressImage(_file)
await LocalStorage.storeImage('avatar', compressedFile)
dispatch(setAvatar(await LocalStorage.getImage('avatar')))
} catch (error: any) {
window.message.error(error.message)
}
}}>
<UserAvatar src={avatar} size="large" />
</Upload>
<SettingRowTitle>{t('settings.theme.window.style.title')}</SettingRowTitle>
<Select
defaultValue={windowStyle || 'opaque'}
style={{ width: 120 }}
onChange={setWindowStyle}
options={[
{ value: 'transparent', label: t('settings.theme.window.style.transparent') },
{ value: 'opaque', label: t('settings.theme.window.style.opaque') }
]}
/>
</SettingRow>
<SettingDivider />
<SettingRow>
@ -98,7 +86,7 @@ const GeneralSettings: FC = () => {
placeholder={t('settings.general.user_name.placeholder')}
value={userName}
onChange={(e) => dispatch(setUserName(e.target.value))}
style={{ width: 150 }}
style={{ width: 170 }}
maxLength={30}
/>
</SettingRow>
@ -109,7 +97,7 @@ const GeneralSettings: FC = () => {
placeholder="socks5://127.0.0.1:6153"
value={proxyUrl}
onChange={(e) => setProxyUrl(e.target.value)}
style={{ width: 300 }}
style={{ width: 170 }}
onBlur={() => onSetProxyUrl()}
type="url"
/>
@ -117,7 +105,7 @@ const GeneralSettings: FC = () => {
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.backup.title')}</SettingRowTitle>
<HStack gap="5px">
<HStack gap="5px" w="170px" justifyContent="space-between">
<Button onClick={backup} icon={<SaveOutlined />}>
{t('settings.general.backup.button')}
</Button>

View File

@ -18,6 +18,7 @@ export interface SettingsState {
messageFont: 'system' | 'serif'
showInputEstimatedTokens: boolean
theme: ThemeMode
windowStyle: 'transparent' | 'opaque'
fontSize: number
}
@ -31,6 +32,7 @@ const initialState: SettingsState = {
messageFont: 'system',
showInputEstimatedTokens: false,
theme: ThemeMode.light,
windowStyle: 'opaque',
fontSize: 14
}
@ -67,6 +69,10 @@ const settingsSlice = createSlice({
},
setFontSize: (state, action: PayloadAction<number>) => {
state.fontSize = action.payload
},
setWindowStyle: (state, action: PayloadAction<'transparent' | 'opaque'>) => {
state.windowStyle = action.payload
console.log(state.windowStyle)
}
}
})
@ -81,7 +87,8 @@ export const {
setMessageFont,
setShowInputEstimatedTokens,
setTheme,
setFontSize
setFontSize,
setWindowStyle
} = settingsSlice.actions
export default settingsSlice.reducer