mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-31 00:10:22 +08:00
* refactor(ui): migrate switch component from heroui to radix-ui replace heroui switch implementation with radix-ui for better maintainability update package.json and yarn.lock to include new dependency * fix(eslint): enable heroui import restriction for deprecated Switch component * refactor(ui): update Switch component props from isSelected/onValueChange to checked/onCheckedChange Standardize Switch component props across the codebase to use checked/onCheckedChange instead of isSelected/onValueChange for better consistency with common React patterns. Also updates loading state prop from isLoading to loading and removes size prop where unnecessary. The changes include: - Replacing isSelected with checked - Replacing onValueChange with onCheckedChange - Updating isLoading to loading - Removing redundant size props - Adjusting styling to accommodate new loading state * refactor(switch): improve switch component styling and structure - Add default values for loading and disabled props - Update styling classes and add group cursor pointer - Restructure loading indicator and thumb positioning - Wrap DescriptionSwitch children in flex container * refactor(ui): improve switch component structure and usage - Restructure DescriptionSwitch to use explicit props instead of children - Add label, description, and position props for better customization - Update all switch usages in SettingsTab to use new props format * refactor(primitives): simplify switch props by omitting children Remove redundant children prop from CustomSwitchProps since it's already omitted from the parent type * fix(switch): add useId for label accessibility in DescriptionSwitch Ensure proper label association with switch input by generating unique ID using React's useId hook * refactor(settings): remove commented out SettingRowTitleSmall components * refactor(SettingsTab): add todo comment for memoization optimization * feat(switch): add size prop to customize switch dimensions Add sm, md, and lg size options to the Switch component with corresponding styles. This allows for better visual consistency across different UI contexts. * style(ui): adjust switch component styling and theme colors update switch component layout and spacing to improve consistency modify secondary-foreground color variable to use correct semantic token * feat(switch): add new switch component styles and animations - Add new switch.css file with gradient and transition styles - Update switch.tsx component with new styling classes and animations - Remove loader icon in favor of animated gradient effect * fix(i18n): Auto update translations for PR #11061 * style(primitives): remove redundant border style from switch component * refactor(switch): remove switch.css and update switch component styles Remove deprecated switch.css file and migrate styles to inline tailwind classes. Update disabled state styling to use opacity instead of linear gradient for better consistency. * refactor(switch): simplify switch thumb implementation Replace complex div structure with svg for loading state Adjust disabled opacity and loading state styling * style(switch): adjust thumb size and positioning for better consistency * feat(switch): add storybook documentation for switch component Add comprehensive Storybook documentation for the Switch component, including: - Basic usage examples - Different states (checked, disabled, loading) - Size variations - DescriptionSwitch variant - Real-world usage scenarios - Accessibility examples - Form integration examples Also remove redundant box-content class from switch styles * fix(switch): adjust thumb positioning for md and lg sizes * style(primitives): improve switch component styling and spacing - Add padding to the container - Simplify label height logic - Update typography classes for better consistency - Adjust switch container alignment * feat(switch): add size prop to DescriptionSwitch component Add support for sm, md, and lg sizes to DescriptionSwitch component with responsive text sizing. Also includes comprehensive Storybook documentation with examples of all sizes and states. * style(switch): align label text to right when isLeftSide is true * refactor(stories): clean up DescriptionSwitch stories by removing unused imports and simplifying JSX * refactor(ui): rename CustomizedSwitch to Switch for consistency Simplify component naming by removing redundant 'Customized' prefix and aligning with common naming conventions * refactor(switch): extract switch root styles into cva variants Improve maintainability by using class-variance-authority to manage switch root styles and variants * refactor(switch): extract thumb variants into separate cva function Improve maintainability by moving switch thumb styling logic into a dedicated variants configuration. This makes the component more readable and easier to modify. * feat(switch): add classNames prop for custom styling Allow custom class names to be applied to switch root, thumb, and thumbSvg elements for more flexible styling options. * feat(switch): add loading animation variants for switch thumb Extract loading animation logic into separate cva variants for better maintainability and reusability --------- Co-authored-by: GitHub Action <action@github.com>
413 lines
14 KiB
TypeScript
413 lines
14 KiB
TypeScript
import { GithubOutlined } from '@ant-design/icons'
|
|
import { Button, RadioGroup, RowFlex, Switch, Tooltip } from '@cherrystudio/ui'
|
|
import { usePreference } from '@data/hooks/usePreference'
|
|
import IndicatorLight from '@renderer/components/IndicatorLight'
|
|
import UpdateDialogPopup from '@renderer/components/Popups/UpdateDialogPopup'
|
|
import { APP_NAME, AppLogo } from '@renderer/config/env'
|
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
|
import { useAppUpdateState } from '@renderer/hooks/useAppUpdate'
|
|
import { useMinappPopup } from '@renderer/hooks/useMinappPopup'
|
|
// import { useRuntime } from '@renderer/hooks/useRuntime'
|
|
import i18n from '@renderer/i18n'
|
|
// import { handleSaveData } from '@renderer/store'
|
|
// import { setUpdateState as setAppUpdateState } from '@renderer/store/runtime'
|
|
import { runAsyncFunction } from '@renderer/utils'
|
|
import { ThemeMode, UpgradeChannel } from '@shared/data/preference/preferenceTypes'
|
|
import { Avatar, Progress, Radio, Row, Tag } from 'antd'
|
|
import { debounce } from 'lodash'
|
|
import { Bug, Building2, Github, Globe, Mail, Rss } from 'lucide-react'
|
|
import { BadgeQuestionMark } from 'lucide-react'
|
|
import type { FC } from 'react'
|
|
import { 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 [autoCheckUpdate, setAutoCheckUpdate] = usePreference('app.dist.auto_update.enabled')
|
|
const [testPlan, setTestPlan] = usePreference('app.dist.test_plan.enabled')
|
|
const [testChannel, setTestChannel] = usePreference('app.dist.test_plan.channel')
|
|
|
|
const [version, setVersion] = useState('')
|
|
const [isPortable, setIsPortable] = useState(false)
|
|
const { t } = useTranslation()
|
|
const { theme } = useTheme()
|
|
// const dispatch = useAppDispatch()
|
|
// const { update } = useRuntime()
|
|
const { openSmartMinapp } = useMinappPopup()
|
|
|
|
const { appUpdateState, updateAppUpdateState } = useAppUpdateState()
|
|
|
|
const onCheckUpdate = debounce(
|
|
async () => {
|
|
if (appUpdateState.checking || appUpdateState.downloading) {
|
|
return
|
|
}
|
|
|
|
if (appUpdateState.downloaded) {
|
|
// Open update dialog directly in renderer
|
|
UpdateDialogPopup.show({ releaseInfo: appUpdateState.info || null })
|
|
return
|
|
}
|
|
|
|
updateAppUpdateState({ checking: true })
|
|
|
|
try {
|
|
await window.api.checkForUpdate()
|
|
} catch (error) {
|
|
window.toast.error(t('settings.about.updateError'))
|
|
}
|
|
|
|
updateAppUpdateState({ 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 debug = async () => {
|
|
await window.api.devTools.toggle()
|
|
}
|
|
|
|
const showEnterprise = async () => {
|
|
onOpenWebsite('https://cherry-ai.com/enterprise')
|
|
}
|
|
|
|
const showReleases = async () => {
|
|
const { appPath } = await window.api.getAppInfo()
|
|
openSmartMinapp({
|
|
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 currentChannelByVersion =
|
|
[
|
|
{ pattern: `-${UpgradeChannel.BETA}.`, channel: UpgradeChannel.BETA },
|
|
{ pattern: `-${UpgradeChannel.RC}.`, channel: UpgradeChannel.RC }
|
|
].find(({ pattern }) => version.includes(pattern))?.channel || UpgradeChannel.LATEST
|
|
|
|
const handleTestChannelChange = async (value: UpgradeChannel) => {
|
|
if (testPlan && currentChannelByVersion !== UpgradeChannel.LATEST && value !== currentChannelByVersion) {
|
|
window.toast.warning(t('settings.general.test_plan.version_channel_not_match'))
|
|
}
|
|
setTestChannel(value)
|
|
// Clear update info when switching upgrade channel
|
|
updateAppUpdateState({
|
|
available: false,
|
|
info: null,
|
|
downloaded: false,
|
|
checking: false,
|
|
downloading: false,
|
|
downloadProgress: 0
|
|
})
|
|
}
|
|
|
|
// Get available test version options based on current version
|
|
const getAvailableTestChannels = () => {
|
|
return [
|
|
{
|
|
tooltip: t('settings.general.test_plan.rc_version_tooltip'),
|
|
label: t('settings.general.test_plan.rc_version'),
|
|
value: UpgradeChannel.RC
|
|
},
|
|
{
|
|
tooltip: t('settings.general.test_plan.beta_version_tooltip'),
|
|
label: t('settings.general.test_plan.beta_version'),
|
|
value: UpgradeChannel.BETA
|
|
}
|
|
]
|
|
}
|
|
|
|
const handleSetTestPlan = (value: boolean) => {
|
|
setTestPlan(value)
|
|
updateAppUpdateState({
|
|
available: false,
|
|
info: null,
|
|
downloaded: false,
|
|
checking: false,
|
|
downloading: false,
|
|
downloadProgress: 0
|
|
})
|
|
|
|
if (value === true) {
|
|
setTestChannel(getTestChannel())
|
|
}
|
|
}
|
|
|
|
const getTestChannel = () => {
|
|
if (testChannel === UpgradeChannel.LATEST) {
|
|
return UpgradeChannel.RC
|
|
}
|
|
return testChannel
|
|
}
|
|
|
|
useEffect(() => {
|
|
runAsyncFunction(async () => {
|
|
const appInfo = await window.api.getAppInfo()
|
|
setVersion(appInfo.version)
|
|
setIsPortable(appInfo.isPortable)
|
|
})
|
|
setAutoCheckUpdate(autoCheckUpdate)
|
|
}, [autoCheckUpdate, setAutoCheckUpdate])
|
|
|
|
const onOpenDocs = () => {
|
|
const isChinese = i18n.language.startsWith('zh')
|
|
window.api.openWebsite(
|
|
isChinese ? 'https://docs.cherry-ai.com/' : 'https://docs.cherry-ai.com/cherry-studio-wen-dang/en-us'
|
|
)
|
|
}
|
|
|
|
return (
|
|
<SettingContainer theme={theme}>
|
|
<SettingGroup theme={theme}>
|
|
<SettingTitle>
|
|
{t('settings.about.title')}
|
|
<RowFlex className="items-center">
|
|
<Link to="https://github.com/CherryHQ/cherry-studio">
|
|
<GithubOutlined style={{ marginRight: 4, color: 'var(--color-text)', fontSize: 20 }} />
|
|
</Link>
|
|
</RowFlex>
|
|
</SettingTitle>
|
|
<SettingDivider />
|
|
<AboutHeader>
|
|
<Row align="middle">
|
|
<AvatarWrapper onClick={() => onOpenWebsite('https://github.com/CherryHQ/cherry-studio')}>
|
|
{appUpdateState.downloadProgress > 0 && (
|
|
<ProgressCircle
|
|
type="circle"
|
|
size={84}
|
|
percent={appUpdateState.downloadProgress}
|
|
showInfo={false}
|
|
strokeLinecap="butt"
|
|
strokeColor="#67ad5b"
|
|
/>
|
|
)}
|
|
<Avatar src={AppLogo} className="h-20 min-h-[80px] w-20" />
|
|
</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>
|
|
{!isPortable && (
|
|
<CheckUpdateButton onClick={onCheckUpdate} disabled={appUpdateState.downloading || appUpdateState.checking}>
|
|
{appUpdateState.downloading
|
|
? t('settings.about.downloading')
|
|
: appUpdateState.available
|
|
? t('settings.about.checkUpdate.available')
|
|
: t('settings.about.checkUpdate.label')}
|
|
</CheckUpdateButton>
|
|
)}
|
|
</AboutHeader>
|
|
{!isPortable && (
|
|
<>
|
|
<SettingDivider />
|
|
<SettingRow>
|
|
<SettingRowTitle>{t('settings.general.auto_check_update.title')}</SettingRowTitle>
|
|
<Switch checked={autoCheckUpdate} onCheckedChange={(v) => setAutoCheckUpdate(v)} />
|
|
</SettingRow>
|
|
<SettingDivider />
|
|
<SettingRow>
|
|
<SettingRowTitle>{t('settings.general.test_plan.title')}</SettingRowTitle>
|
|
<Tooltip content={t('settings.general.test_plan.tooltip')}>
|
|
<Switch checked={testPlan} onCheckedChange={(v) => handleSetTestPlan(v)} />
|
|
</Tooltip>
|
|
</SettingRow>
|
|
{testPlan && (
|
|
<>
|
|
<SettingDivider />
|
|
<SettingRow>
|
|
<SettingRowTitle>{t('settings.general.test_plan.version_options')}</SettingRowTitle>
|
|
<RadioGroup
|
|
orientation="horizontal"
|
|
value={getTestChannel()}
|
|
onValueChange={(value) => handleTestChannelChange(value as UpgradeChannel)}>
|
|
{getAvailableTestChannels().map((option) => (
|
|
<Tooltip key={option.value} content={option.tooltip}>
|
|
<Radio value={option.value}>{option.label}</Radio>
|
|
</Tooltip>
|
|
))}
|
|
</RadioGroup>
|
|
</SettingRow>
|
|
</>
|
|
)}
|
|
</>
|
|
)}
|
|
</SettingGroup>
|
|
{appUpdateState.info && appUpdateState.available && (
|
|
<SettingGroup theme={theme}>
|
|
<SettingRow>
|
|
<SettingRowTitle>
|
|
{t('settings.about.updateAvailable', { version: appUpdateState.info.version })}
|
|
<IndicatorLight color="green" />
|
|
</SettingRowTitle>
|
|
</SettingRow>
|
|
<UpdateNotesWrapper className="markdown">
|
|
<Markdown>
|
|
{typeof appUpdateState.info.releaseNotes === 'string'
|
|
? appUpdateState.info.releaseNotes.replace(/\n/g, '\n\n')
|
|
: appUpdateState.info.releaseNotes?.map((note) => note.note).join('\n')}
|
|
</Markdown>
|
|
</UpdateNotesWrapper>
|
|
</SettingGroup>
|
|
)}
|
|
<SettingGroup theme={theme}>
|
|
<SettingRow>
|
|
<SettingRowTitle>
|
|
<BadgeQuestionMark size={18} />
|
|
{t('docs.title')}
|
|
</SettingRowTitle>
|
|
<Button onClick={onOpenDocs}>{t('settings.about.website.button')}</Button>
|
|
</SettingRow>
|
|
<SettingDivider />
|
|
<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>
|
|
<Building2 size={18} />
|
|
{t('settings.about.enterprise.title')}
|
|
</SettingRowTitle>
|
|
<Button onClick={showEnterprise}>{t('settings.about.website.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>
|
|
<SettingDivider />
|
|
<SettingRow>
|
|
<SettingRowTitle>
|
|
<Bug size={18} />
|
|
{t('settings.about.debug.title')}
|
|
</SettingRowTitle>
|
|
<Button onClick={debug}>{t('settings.about.debug.open')}</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;
|
|
color: var(--color-text-2);
|
|
font-size: 14px;
|
|
|
|
p {
|
|
margin: 0;
|
|
}
|
|
`
|
|
|
|
export default AboutSettings
|