cherry-studio/src/renderer/src/pages/settings/AboutSettings.tsx

291 lines
9.0 KiB
TypeScript

import { GithubOutlined } from '@ant-design/icons'
import IndicatorLight from '@renderer/components/IndicatorLight'
import { HStack } from '@renderer/components/Layout'
import { APP_NAME, AppLogo } from '@renderer/config/env'
import { useTheme } from '@renderer/context/ThemeProvider'
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
import { useRuntime } from '@renderer/hooks/useRuntime'
import { useSettings } from '@renderer/hooks/useSettings'
import { useAppDispatch } from '@renderer/store'
import { setUpdateState } from '@renderer/store/runtime'
import { ThemeMode } from '@renderer/types'
import { compareVersions, runAsyncFunction } from '@renderer/utils'
import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd'
import { debounce } from 'lodash'
import { FileCheck, Github, Globe, Mail, Rss } from 'lucide-react'
import { FC, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Markdown from 'react-markdown'
import { Link } from 'react-router-dom'
import styled from 'styled-components'
import { SettingContainer, SettingDivider, SettingGroup, SettingRow, SettingTitle } from '.'
const AboutSettings: FC = () => {
const [version, setVersion] = useState('')
const { t } = useTranslation()
const { autoCheckUpdate, setAutoCheckUpdate } = useSettings()
const { theme } = useTheme()
const dispatch = useAppDispatch()
const { update } = useRuntime()
const { openMinapp } = useMinappPopup()
const onCheckUpdate = debounce(
async () => {
if (update.checking || update.downloading) {
return
}
if (update.downloaded) {
window.api.showUpdateDialog()
return
}
dispatch(setUpdateState({ checking: true }))
try {
await window.api.checkForUpdate()
} catch (error) {
window.message.error(t('settings.about.updateError'))
}
dispatch(setUpdateState({ checking: false }))
},
2000,
{ leading: true, trailing: false }
)
const onOpenWebsite = (url: string) => {
window.api.openWebsite(url)
}
const mailto = async () => {
const email = 'support@cherry-ai.com'
const subject = `${APP_NAME} Feedback`
const version = (await window.api.getAppInfo()).version
const platform = window.electron.process.platform
const url = `mailto:${email}?subject=${subject}&body=%0A%0AVersion: ${version} | Platform: ${platform}`
onOpenWebsite(url)
}
const showLicense = async () => {
const { appPath } = await window.api.getAppInfo()
openMinapp({
id: 'cherrystudio-license',
name: t('settings.about.license.title'),
url: `file://${appPath}/resources/cherry-studio/license.html`,
logo: AppLogo
})
}
const showReleases = async () => {
const { appPath } = await window.api.getAppInfo()
openMinapp({
id: 'cherrystudio-releases',
name: t('settings.about.releases.title'),
url: `file://${appPath}/resources/cherry-studio/releases.html?theme=${theme === ThemeMode.dark ? 'dark' : 'light'}`,
logo: AppLogo
})
}
const hasNewVersion = update?.info?.version && version ? compareVersions(update.info.version, version) > 0 : false
useEffect(() => {
runAsyncFunction(async () => {
const appInfo = await window.api.getAppInfo()
setVersion(appInfo.version)
})
}, [])
return (
<SettingContainer theme={theme}>
<SettingGroup theme={theme}>
<SettingTitle>
{t('settings.about.title')}
<HStack alignItems="center">
<Link to="https://github.com/CherryHQ/cherry-studio">
<GithubOutlined style={{ marginRight: 4, color: 'var(--color-text)', fontSize: 20 }} />
</Link>
</HStack>
</SettingTitle>
<SettingDivider />
<AboutHeader>
<Row align="middle">
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio')}>
{update.downloadProgress > 0 && (
<ProgressCircle
type="circle"
size={84}
percent={update.downloadProgress}
showInfo={false}
strokeLinecap="butt"
strokeColor="#67ad5b"
/>
)}
<Avatar src={AppLogo} size={80} style={{ minHeight: 80 }} />
</AvatarWrapper>
<VersionWrapper>
<Title>{APP_NAME}</Title>
<Description>{t('settings.about.description')}</Description>
<Tag
onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio/releases')}
color="cyan"
style={{ marginTop: 8, cursor: 'pointer' }}>
v{version}
</Tag>
</VersionWrapper>
</Row>
<CheckUpdateButton
onClick={onCheckUpdate}
loading={update.checking}
disabled={update.downloading || update.checking}>
{update.downloading
? t('settings.about.downloading')
: update.available
? t('settings.about.checkUpdate.available')
: t('settings.about.checkUpdate')}
</CheckUpdateButton>
</AboutHeader>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.auto_check_update.title')}</SettingRowTitle>
<Switch value={autoCheckUpdate} onChange={(v) => setAutoCheckUpdate(v)} />
</SettingRow>
</SettingGroup>
{hasNewVersion && update.info && (
<SettingGroup theme={theme}>
<SettingRow>
<SettingRowTitle>
{t('settings.about.updateAvailable', { version: update.info.version })}
<IndicatorLight color="green" />
</SettingRowTitle>
</SettingRow>
<UpdateNotesWrapper>
<Markdown>
{typeof update.info.releaseNotes === 'string'
? update.info.releaseNotes.replace(/\n/g, '\n\n')
: update.info.releaseNotes?.map((note) => note.note).join('\n')}
</Markdown>
</UpdateNotesWrapper>
</SettingGroup>
)}
<SettingGroup theme={theme}>
<SettingRow>
<SettingRowTitle>
<Rss size={18} />
{t('settings.about.releases.title')}
</SettingRowTitle>
<Button onClick={showReleases}>{t('settings.about.releases.button')}</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
<Globe size={18} />
{t('settings.about.website.title')}
</SettingRowTitle>
<Button onClick={() => onOpenWebsite('https://cherry-ai.com')}>{t('settings.about.website.button')}</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
<Github size={18} />
{t('settings.about.feedback.title')}
</SettingRowTitle>
<Button onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio/issues/new/choose')}>
{t('settings.about.feedback.button')}
</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
<FileCheck size={18} />
{t('settings.about.license.title')}
</SettingRowTitle>
<Button onClick={showLicense}>{t('settings.about.license.button')}</Button>
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>
<Mail size={18} />
{t('settings.about.contact.title')}
</SettingRowTitle>
<Button onClick={mailto}>{t('settings.about.contact.button')}</Button>
</SettingRow>
</SettingGroup>
</SettingContainer>
)
}
const AboutHeader = styled.div`
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 5px 0;
`
const VersionWrapper = styled.div`
display: flex;
flex-direction: column;
min-height: 80px;
justify-content: center;
align-items: flex-start;
`
const Title = styled.div`
font-size: 20px;
font-weight: bold;
color: var(--color-text-1);
margin-bottom: 5px;
`
const Description = styled.div`
font-size: 14px;
color: var(--color-text-2);
text-align: center;
`
const CheckUpdateButton = styled(Button)``
const AvatarWrapper = styled.div`
position: relative;
cursor: pointer;
margin-right: 15px;
`
const ProgressCircle = styled(Progress)`
position: absolute;
top: -2px;
left: -2px;
`
export const SettingRowTitle = styled.div`
font-size: 14px;
line-height: 18px;
color: var(--color-text-1);
display: flex;
flex-direction: row;
align-items: center;
gap: 10px;
.anticon {
font-size: 16px;
color: var(--color-text-1);
}
`
const UpdateNotesWrapper = styled.div`
padding: 12px 0;
margin: 8px 0;
background-color: var(--color-bg-2);
border-radius: 6px;
p {
margin: 0;
color: var(--color-text-2);
font-size: 14px;
}
`
export default AboutSettings