mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-25 03:10:08 +08:00
feat: make sidebar setting group collapsible (#6019)
* feat: code tools, editor, executor CodeEditor & Preview - CodeEditor: CodeMirror 6 - Switch to CodeEditor in the settings - Support edit&save with a accurate diff&lookup strategy - Use CodeEditor for editing MCP json configuration - CodePreview: Original Shiki syntax highlighting - Implemented using a custom Shiki stream tokenizer - Remov code caching as it is incompatible with the current streaming code highlighting - Add a webworker for shiki - Other preview components - Merge MermaidPopup and Mermaid to MermaidPreview, use local mermaidjs - Show mermaid syntax error message on demand - Rename PlantUML to PlantUmlPreview - Rename SyntaxHighlighterProvider to CodeStyleProvider for clarity - Both light and dark themes are preserved for convenience CodeToolbar - Top sticky toolbar provides quick tools (left) and core tools (right) - Quick tools are hidden under the `More` button to avoid clutter, while core tools are always visible - View&edit mode - Allow switching between preview and edit modes - Add a split view Code execution - Pyodide for executing Python scripts - Add a webworker for Pyodide * fix: migrate version and lint error * refactor: use constants for defining tool specs * feat: make setting group collapsible * fix: yarn.lock * fix: conflict error --------- Co-authored-by: one <wangan.cs@gmail.com>
This commit is contained in:
parent
9d0b5d2c8f
commit
efd11457db
@ -11,8 +11,9 @@ import {
|
||||
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
|
||||
import { useAssistant } from '@renderer/hooks/useAssistant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@renderer/pages/settings'
|
||||
import { SettingDivider, SettingRow, SettingRowTitle } from '@renderer/pages/settings'
|
||||
import AssistantSettingsPopup from '@renderer/pages/settings/AssistantSettings'
|
||||
import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
import {
|
||||
SendMessageShortcut,
|
||||
@ -49,7 +50,7 @@ import {
|
||||
TranslateLanguageVarious
|
||||
} from '@renderer/types'
|
||||
import { modalConfirm } from '@renderer/utils'
|
||||
import { Button, Col, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { Button, Col, Divider, InputNumber, Row, Select, Slider, Switch, Tooltip } from 'antd'
|
||||
import { CircleHelp, RotateCcw, Settings2 } from 'lucide-react'
|
||||
import { FC, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -181,478 +182,485 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
|
||||
return (
|
||||
<Container className="settings-tab">
|
||||
<SettingGroup style={{ marginTop: 10 }}>
|
||||
<SettingSubtitle style={{ marginTop: 0, display: 'flex', justifyContent: 'space-between' }}>
|
||||
<CollapsibleSettingGroup
|
||||
title={t('assistants.settings.title')}
|
||||
defaultExpanded={true}
|
||||
extra={
|
||||
<HStack alignItems="center">
|
||||
{t('assistants.settings.title')}{' '}
|
||||
<Tooltip title={t('chat.settings.reset')}>
|
||||
<RotateCcw size={20} onClick={onReset} style={{ cursor: 'pointer', padding: '0 3px' }} />
|
||||
</Tooltip>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<Settings2 size={16} />}
|
||||
onClick={() => AssistantSettingsPopup.show({ assistant, tab: 'model' })}
|
||||
/>
|
||||
</HStack>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
icon={<Settings2 size={16} />}
|
||||
onClick={() => AssistantSettingsPopup.show({ assistant, tab: 'model' })}
|
||||
/>
|
||||
</SettingSubtitle>
|
||||
<SettingDivider />
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.temperature')}</Label>
|
||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||
<CircleHelp size={14} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={2}
|
||||
onChange={setTemperature}
|
||||
onChangeComplete={onTemperatureChange}
|
||||
value={typeof temperature === 'number' ? temperature : 0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.context_count')}</Label>
|
||||
<Tooltip title={t('chat.settings.context_count.tip')}>
|
||||
<CircleHelp size={14} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={maxContextCount}
|
||||
onChange={setContextCount}
|
||||
onChangeComplete={onContextCountChange}
|
||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||
step={1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('models.stream_output')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={streamOutput}
|
||||
onChange={(checked) => {
|
||||
setStreamOutput(checked)
|
||||
onUpdateAssistantSettings({ streamOutput: checked })
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<Row align="middle" justify="space-between" style={{ marginBottom: 10 }}>
|
||||
<HStack alignItems="center">
|
||||
<Label>{t('chat.settings.max_tokens')}</Label>
|
||||
<Tooltip title={t('chat.settings.max_tokens.tip')}>
|
||||
}>
|
||||
<SettingGroup style={{ marginTop: 10 }}>
|
||||
<SettingDivider />
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.temperature')}</Label>
|
||||
<Tooltip title={t('chat.settings.temperature.tip')}>
|
||||
<CircleHelp size={14} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableMaxTokens}
|
||||
onChange={async (enabled) => {
|
||||
if (enabled) {
|
||||
const confirmed = await modalConfirm({
|
||||
title: t('chat.settings.max_tokens.confirm'),
|
||||
content: t('chat.settings.max_tokens.confirm_content'),
|
||||
okButtonProps: {
|
||||
danger: true
|
||||
}
|
||||
})
|
||||
if (!confirmed) return
|
||||
}
|
||||
setEnableMaxTokens(enabled)
|
||||
onUpdateAssistantSettings({ enableMaxTokens: enabled })
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
{enableMaxTokens && (
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<InputNumber
|
||||
disabled={!enableMaxTokens}
|
||||
<Slider
|
||||
min={0}
|
||||
max={10000000}
|
||||
step={100}
|
||||
value={typeof maxTokens === 'number' ? maxTokens : 0}
|
||||
changeOnBlur
|
||||
onChange={(value) => value && setMaxTokens(value)}
|
||||
onBlur={() => onMaxTokensChange(maxTokens)}
|
||||
style={{ width: '100%' }}
|
||||
max={2}
|
||||
onChange={setTemperature}
|
||||
onChangeComplete={onTemperatureChange}
|
||||
value={typeof temperature === 'number' ? temperature : 0}
|
||||
step={0.1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</SettingGroup>
|
||||
<SettingGroup>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('settings.messages.title')}</SettingSubtitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.prompt')}</SettingRowTitleSmall>
|
||||
<Switch size="small" checked={showPrompt} onChange={(checked) => dispatch(setShowPrompt(checked))} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.divider')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showMessageDivider}
|
||||
onChange={(checked) => dispatch(setShowMessageDivider(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.use_serif_font')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={messageFont === 'serif'}
|
||||
onChange={(checked) => dispatch(setMessageFont(checked ? 'serif' : 'system'))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.thought_auto_collapse')}
|
||||
<Tooltip title={t('chat.settings.thought_auto_collapse.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
<Row align="middle">
|
||||
<Label>{t('chat.settings.context_count')}</Label>
|
||||
<Tooltip title={t('chat.settings.context_count.tip')}>
|
||||
<CircleHelp size={14} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={thoughtAutoCollapse}
|
||||
onChange={(checked) => dispatch(setThoughtAutoCollapse(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={messageStyle}
|
||||
onChange={(value) => dispatch(setMessageStyle(value as 'plain' | 'bubble'))}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
<Select.Option value="plain">{t('message.message.style.plain')}</Select.Option>
|
||||
<Select.Option value="bubble">{t('message.message.style.bubble')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.multi_model_style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={multiModelMessageStyle}
|
||||
onChange={(value) =>
|
||||
dispatch(setMultiModelMessageStyle(value as 'fold' | 'vertical' | 'horizontal' | 'grid'))
|
||||
}
|
||||
style={{ width: 135 }}>
|
||||
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
|
||||
<Select.Option value="vertical">{t('message.message.multi_model_style.vertical')}</Select.Option>
|
||||
<Select.Option value="horizontal">{t('message.message.multi_model_style.horizontal')}</Select.Option>
|
||||
<Select.Option value="grid">{t('message.message.multi_model_style.grid')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.navigation')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={messageNavigation}
|
||||
onChange={(value) => dispatch(setMessageNavigation(value as 'none' | 'buttons' | 'anchor'))}
|
||||
style={{ width: 135 }}>
|
||||
<Select.Option value="none">{t('settings.messages.navigation.none')}</Select.Option>
|
||||
<Select.Option value="buttons">{t('settings.messages.navigation.buttons')}</Select.Option>
|
||||
<Select.Option value="anchor">{t('settings.messages.navigation.anchor')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.math_engine')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={mathEngine}
|
||||
onChange={(value) => dispatch(setMathEngine(value as MathEngine))}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
<Select.Option value="KaTeX">KaTeX</Select.Option>
|
||||
<Select.Option value="MathJax">MathJax</Select.Option>
|
||||
<Select.Option value="none">{t('settings.messages.math_engine.none')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.font_size.title')}</SettingRowTitleSmall>
|
||||
</SettingRow>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
value={fontSizeValue}
|
||||
onChange={(value) => setFontSizeValue(value)}
|
||||
onChangeComplete={(value) => dispatch(setFontSize(value))}
|
||||
min={12}
|
||||
max={22}
|
||||
step={1}
|
||||
marks={{
|
||||
12: <span style={{ fontSize: '12px' }}>A</span>,
|
||||
14: <span style={{ fontSize: '14px' }}>{t('common.default')}</span>,
|
||||
22: <span style={{ fontSize: '18px' }}>A</span>
|
||||
</Row>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
min={0}
|
||||
max={maxContextCount}
|
||||
onChange={setContextCount}
|
||||
onChangeComplete={onContextCountChange}
|
||||
value={typeof contextCount === 'number' ? contextCount : 0}
|
||||
step={1}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider style={{ margin: '10px 0' }} />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('models.stream_output')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={streamOutput}
|
||||
onChange={(checked) => {
|
||||
setStreamOutput(checked)
|
||||
onUpdateAssistantSettings({ streamOutput: checked })
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</SettingGroup>
|
||||
<SettingGroup>
|
||||
<SettingSubtitle style={{ marginTop: 0 }}>{t('chat.settings.code.title')}</SettingSubtitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={codeStyle}
|
||||
onChange={(value) => onCodeStyleChange(value as CodeStyleVarious)}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
{themeNames.map((theme) => (
|
||||
<Select.Option key={theme} value={theme}>
|
||||
{theme}
|
||||
</Select.Option>
|
||||
))}
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_execution.title')}
|
||||
<Tooltip title={t('chat.settings.code_execution.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeExecution.enabled}
|
||||
onChange={(checked) => dispatch(setCodeExecution({ enabled: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{codeExecution.enabled && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_execution.timeout_minutes')}
|
||||
<Tooltip title={t('chat.settings.code_execution.timeout_minutes.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
max={60}
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<Row align="middle" justify="space-between" style={{ marginBottom: 10 }}>
|
||||
<HStack alignItems="center">
|
||||
<Label>{t('chat.settings.max_tokens')}</Label>
|
||||
<Tooltip title={t('chat.settings.max_tokens.tip')}>
|
||||
<CircleHelp size={14} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</HStack>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableMaxTokens}
|
||||
onChange={async (enabled) => {
|
||||
if (enabled) {
|
||||
const confirmed = await modalConfirm({
|
||||
title: t('chat.settings.max_tokens.confirm'),
|
||||
content: t('chat.settings.max_tokens.confirm_content'),
|
||||
okButtonProps: {
|
||||
danger: true
|
||||
}
|
||||
})
|
||||
if (!confirmed) return
|
||||
}
|
||||
setEnableMaxTokens(enabled)
|
||||
onUpdateAssistantSettings({ enableMaxTokens: enabled })
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
{enableMaxTokens && (
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<InputNumber
|
||||
disabled={!enableMaxTokens}
|
||||
min={0}
|
||||
max={10000000}
|
||||
step={100}
|
||||
value={typeof maxTokens === 'number' ? maxTokens : 0}
|
||||
changeOnBlur
|
||||
onChange={(value) => value && setMaxTokens(value)}
|
||||
onBlur={() => onMaxTokensChange(maxTokens)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
)}
|
||||
</SettingGroup>
|
||||
</CollapsibleSettingGroup>
|
||||
<CollapsibleSettingGroup title={t('settings.messages.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.prompt')}</SettingRowTitleSmall>
|
||||
<Switch size="small" checked={showPrompt} onChange={(checked) => dispatch(setShowPrompt(checked))} />
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.divider')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showMessageDivider}
|
||||
onChange={(checked) => dispatch(setShowMessageDivider(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.use_serif_font')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={messageFont === 'serif'}
|
||||
onChange={(checked) => dispatch(setMessageFont(checked ? 'serif' : 'system'))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.thought_auto_collapse')}
|
||||
<Tooltip title={t('chat.settings.thought_auto_collapse.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={thoughtAutoCollapse}
|
||||
onChange={(checked) => dispatch(setThoughtAutoCollapse(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={messageStyle}
|
||||
onChange={(value) => dispatch(setMessageStyle(value as 'plain' | 'bubble'))}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
<Select.Option value="plain">{t('message.message.style.plain')}</Select.Option>
|
||||
<Select.Option value="bubble">{t('message.message.style.bubble')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.multi_model_style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={multiModelMessageStyle}
|
||||
onChange={(value) =>
|
||||
dispatch(setMultiModelMessageStyle(value as 'fold' | 'vertical' | 'horizontal' | 'grid'))
|
||||
}
|
||||
style={{ width: 135 }}>
|
||||
<Select.Option value="fold">{t('message.message.multi_model_style.fold')}</Select.Option>
|
||||
<Select.Option value="vertical">{t('message.message.multi_model_style.vertical')}</Select.Option>
|
||||
<Select.Option value="horizontal">{t('message.message.multi_model_style.horizontal')}</Select.Option>
|
||||
<Select.Option value="grid">{t('message.message.multi_model_style.grid')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.navigation')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={messageNavigation}
|
||||
onChange={(value) => dispatch(setMessageNavigation(value as 'none' | 'buttons' | 'anchor'))}
|
||||
style={{ width: 135 }}>
|
||||
<Select.Option value="none">{t('settings.messages.navigation.none')}</Select.Option>
|
||||
<Select.Option value="buttons">{t('settings.messages.navigation.buttons')}</Select.Option>
|
||||
<Select.Option value="anchor">{t('settings.messages.navigation.anchor')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.math_engine')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={mathEngine}
|
||||
onChange={(value) => dispatch(setMathEngine(value as MathEngine))}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
<Select.Option value="KaTeX">KaTeX</Select.Option>
|
||||
<Select.Option value="MathJax">MathJax</Select.Option>
|
||||
<Select.Option value="none">{t('settings.messages.math_engine.none')}</Select.Option>
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.font_size.title')}</SettingRowTitleSmall>
|
||||
</SettingRow>
|
||||
<Row align="middle" gutter={10}>
|
||||
<Col span={24}>
|
||||
<Slider
|
||||
value={fontSizeValue}
|
||||
onChange={(value) => setFontSizeValue(value)}
|
||||
onChangeComplete={(value) => dispatch(setFontSize(value))}
|
||||
min={12}
|
||||
max={22}
|
||||
step={1}
|
||||
value={codeExecution.timeoutMinutes}
|
||||
onChange={(value) => dispatch(setCodeExecution({ timeoutMinutes: value ?? 1 }))}
|
||||
style={{ width: 80 }}
|
||||
marks={{
|
||||
12: <span style={{ fontSize: '12px' }}>A</span>,
|
||||
14: <span style={{ fontSize: '14px' }}>{t('common.default')}</span>,
|
||||
22: <span style={{ fontSize: '18px' }}>A</span>
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.title')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.enabled}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ enabled: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{codeEditor.enabled && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.highlight_active_line')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.highlightActiveLine}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ highlightActiveLine: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.fold_gutter')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.foldGutter}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ foldGutter: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.autocompletion')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.autocompletion}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ autocompletion: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.keymap')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.keymap}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ keymap: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.show_line_numbers')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeShowLineNumbers}
|
||||
onChange={(checked) => dispatch(setCodeShowLineNumbers(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_collapsible')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeCollapsible}
|
||||
onChange={(checked) => dispatch(setCodeCollapsible(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_wrappable')}</SettingRowTitleSmall>
|
||||
<Switch size="small" checked={codeWrappable} onChange={(checked) => dispatch(setCodeWrappable(checked))} />
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
<SettingGroup>
|
||||
<SettingSubtitle style={{ marginTop: 10 }}>{t('settings.messages.input.title')}</SettingSubtitle>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showInputEstimatedTokens}
|
||||
onChange={(checked) => dispatch(setShowInputEstimatedTokens(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.paste_long_text_as_file')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={pasteLongTextAsFile}
|
||||
onChange={(checked) => dispatch(setPasteLongTextAsFile(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{pasteLongTextAsFile && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.paste_long_text_threshold')}</SettingRowTitleSmall>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={500}
|
||||
max={10000}
|
||||
step={100}
|
||||
value={pasteLongTextThreshold}
|
||||
onChange={(value) => dispatch(setPasteLongTextThreshold(value ?? 500))}
|
||||
style={{ width: 80 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.markdown_rendering_input_message')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={renderInputMessageAsMarkdown}
|
||||
onChange={(checked) => dispatch(setRenderInputMessageAsMarkdown(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
{!language.startsWith('en') && (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.auto_translate_with_space')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={autoTranslateWithSpace}
|
||||
onChange={(checked) => dispatch(setAutoTranslateWithSpace(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
</>
|
||||
)}
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.show_translate_confirm')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showTranslateConfirm}
|
||||
onChange={(checked) => dispatch(setShowTranslateConfirm(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.enable_quick_triggers')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableQuickPanelTriggers}
|
||||
onChange={(checked) => dispatch(setEnableQuickPanelTriggers(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.enable_delete_model')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableBackspaceDeleteModel}
|
||||
onChange={(checked) => dispatch(setEnableBackspaceDeleteModel(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
defaultValue={'english' as TranslateLanguageVarious}
|
||||
size="small"
|
||||
value={targetLanguage}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
options={[
|
||||
{ value: 'chinese', label: t('settings.input.target_language.chinese') },
|
||||
{ value: 'chinese-traditional', label: t('settings.input.target_language.chinese-traditional') },
|
||||
{ value: 'english', label: t('settings.input.target_language.english') },
|
||||
{ value: 'japanese', label: t('settings.input.target_language.japanese') },
|
||||
{ value: 'russian', label: t('settings.input.target_language.russian') }
|
||||
]}
|
||||
onChange={(value) => setTargetLanguage(value as TranslateLanguageVarious)}
|
||||
style={{ width: 135 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.send_shortcuts')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={sendMessageShortcut}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
options={[
|
||||
{ value: 'Enter', label: 'Enter' },
|
||||
{ value: 'Shift+Enter', label: 'Shift + Enter' },
|
||||
{ value: 'Ctrl+Enter', label: 'Ctrl + Enter' },
|
||||
{ value: 'Command+Enter', label: `${isMac ? '⌘' : isWindows ? 'Win' : 'Super'} + Enter` }
|
||||
]}
|
||||
onChange={(value) => setSendMessageShortcut(value as SendMessageShortcut)}
|
||||
style={{ width: 135 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</SettingGroup>
|
||||
</CollapsibleSettingGroup>
|
||||
<CollapsibleSettingGroup title={t('chat.settings.code.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('message.message.code_style')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
value={codeStyle}
|
||||
onChange={(value) => onCodeStyleChange(value as CodeStyleVarious)}
|
||||
style={{ width: 135 }}
|
||||
size="small">
|
||||
{themeNames.map((theme) => (
|
||||
<Select.Option key={theme} value={theme}>
|
||||
{theme}
|
||||
</Select.Option>
|
||||
))}
|
||||
</StyledSelect>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_execution.title')}
|
||||
<Tooltip title={t('chat.settings.code_execution.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeExecution.enabled}
|
||||
onChange={(checked) => dispatch(setCodeExecution({ enabled: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{codeExecution.enabled && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_execution.timeout_minutes')}
|
||||
<Tooltip title={t('chat.settings.code_execution.timeout_minutes.tip')}>
|
||||
<CircleHelp size={14} style={{ marginLeft: 4 }} color="var(--color-text-2)" />
|
||||
</Tooltip>
|
||||
</SettingRowTitleSmall>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={1}
|
||||
max={60}
|
||||
step={1}
|
||||
value={codeExecution.timeoutMinutes}
|
||||
onChange={(value) => dispatch(setCodeExecution({ timeoutMinutes: value ?? 1 }))}
|
||||
style={{ width: 80 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.title')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.enabled}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ enabled: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{codeEditor.enabled && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.highlight_active_line')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.highlightActiveLine}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ highlightActiveLine: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.fold_gutter')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.foldGutter}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ foldGutter: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.autocompletion')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.autocompletion}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ autocompletion: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow style={{ paddingLeft: 8 }}>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_editor.keymap')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeEditor.keymap}
|
||||
onChange={(checked) => dispatch(setCodeEditor({ keymap: checked }))}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.show_line_numbers')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeShowLineNumbers}
|
||||
onChange={(checked) => dispatch(setCodeShowLineNumbers(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_collapsible')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeCollapsible}
|
||||
onChange={(checked) => dispatch(setCodeCollapsible(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('chat.settings.code_wrappable')}</SettingRowTitleSmall>
|
||||
<Switch size="small" checked={codeWrappable} onChange={(checked) => dispatch(setCodeWrappable(checked))} />
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
</CollapsibleSettingGroup>
|
||||
<CollapsibleSettingGroup title={t('settings.messages.input.title')} defaultExpanded={true}>
|
||||
<SettingGroup>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.show_estimated_tokens')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showInputEstimatedTokens}
|
||||
onChange={(checked) => dispatch(setShowInputEstimatedTokens(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.paste_long_text_as_file')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={pasteLongTextAsFile}
|
||||
onChange={(checked) => dispatch(setPasteLongTextAsFile(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
{pasteLongTextAsFile && (
|
||||
<>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.paste_long_text_threshold')}</SettingRowTitleSmall>
|
||||
<InputNumber
|
||||
size="small"
|
||||
min={500}
|
||||
max={10000}
|
||||
step={100}
|
||||
value={pasteLongTextThreshold}
|
||||
onChange={(value) => dispatch(setPasteLongTextThreshold(value ?? 500))}
|
||||
style={{ width: 80 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
</>
|
||||
)}
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.markdown_rendering_input_message')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={renderInputMessageAsMarkdown}
|
||||
onChange={(checked) => dispatch(setRenderInputMessageAsMarkdown(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
{!language.startsWith('en') && (
|
||||
<>
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.auto_translate_with_space')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={autoTranslateWithSpace}
|
||||
onChange={(checked) => dispatch(setAutoTranslateWithSpace(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
</>
|
||||
)}
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.show_translate_confirm')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={showTranslateConfirm}
|
||||
onChange={(checked) => dispatch(setShowTranslateConfirm(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.enable_quick_triggers')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableQuickPanelTriggers}
|
||||
onChange={(checked) => dispatch(setEnableQuickPanelTriggers(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.enable_delete_model')}</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={enableBackspaceDeleteModel}
|
||||
onChange={(checked) => dispatch(setEnableBackspaceDeleteModel(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.input.target_language')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
defaultValue={'english' as TranslateLanguageVarious}
|
||||
size="small"
|
||||
value={targetLanguage}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
options={[
|
||||
{ value: 'chinese', label: t('settings.input.target_language.chinese') },
|
||||
{ value: 'chinese-traditional', label: t('settings.input.target_language.chinese-traditional') },
|
||||
{ value: 'english', label: t('settings.input.target_language.english') },
|
||||
{ value: 'japanese', label: t('settings.input.target_language.japanese') },
|
||||
{ value: 'russian', label: t('settings.input.target_language.russian') }
|
||||
]}
|
||||
onChange={(value) => setTargetLanguage(value as TranslateLanguageVarious)}
|
||||
style={{ width: 135 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>{t('settings.messages.input.send_shortcuts')}</SettingRowTitleSmall>
|
||||
<StyledSelect
|
||||
size="small"
|
||||
value={sendMessageShortcut}
|
||||
menuItemSelectedIcon={<CheckOutlined />}
|
||||
options={[
|
||||
{ value: 'Enter', label: 'Enter' },
|
||||
{ value: 'Shift+Enter', label: 'Shift + Enter' },
|
||||
{ value: 'Ctrl+Enter', label: 'Ctrl + Enter' },
|
||||
{ value: 'Command+Enter', label: `${isMac ? '⌘' : isWindows ? 'Win' : 'Super'} + Enter` }
|
||||
]}
|
||||
onChange={(value) => setSendMessageShortcut(value as SendMessageShortcut)}
|
||||
style={{ width: 135 }}
|
||||
/>
|
||||
</SettingRow>
|
||||
</SettingGroup>
|
||||
</CollapsibleSettingGroup>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
@ -677,7 +685,7 @@ const SettingRowTitleSmall = styled(SettingRowTitle)`
|
||||
font-size: 13px;
|
||||
`
|
||||
|
||||
export const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
padding: 0 5px;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
|
||||
62
src/renderer/src/pages/settings/SettingGroup.tsx
Normal file
62
src/renderer/src/pages/settings/SettingGroup.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
import { ThemeMode } from '@renderer/types'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
import { ChevronDown, ChevronRight } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
export const CollapsibleSettingGroup = styled(({ title, children, defaultExpanded = true, extra, ...rest }) => {
|
||||
const [isExpanded, setIsExpanded] = useState(defaultExpanded)
|
||||
|
||||
return (
|
||||
<SettingGroup {...rest}>
|
||||
<GroupHeader>
|
||||
<div
|
||||
onClick={() => setIsExpanded(!isExpanded)}
|
||||
style={{ display: 'flex', alignItems: 'center', flex: 1, cursor: 'pointer' }}>
|
||||
{isExpanded ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
|
||||
<GroupTitle>{title}</GroupTitle>
|
||||
</div>
|
||||
{extra && <div>{extra}</div>}
|
||||
</GroupHeader>
|
||||
<AnimatePresence initial={false}>
|
||||
{isExpanded && (
|
||||
<ContentContainer
|
||||
initial={{ height: 0, opacity: 0 }}
|
||||
animate={{ height: 'auto', opacity: 1 }}
|
||||
exit={{ height: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.2 }}>
|
||||
<div>{children}</div>
|
||||
</ContentContainer>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</SettingGroup>
|
||||
)
|
||||
})`
|
||||
margin-bottom: 4px;
|
||||
`
|
||||
|
||||
const SettingGroup = styled.div<{ theme?: ThemeMode }>`
|
||||
padding: 0 5px;
|
||||
width: 100%;
|
||||
margin-top: 0;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
`
|
||||
|
||||
const GroupHeader = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 6px 0;
|
||||
user-select: none;
|
||||
`
|
||||
|
||||
const GroupTitle = styled.div`
|
||||
font-weight: 500;
|
||||
margin-left: 4px;
|
||||
font-size: 14px;
|
||||
`
|
||||
|
||||
const ContentContainer = styled(motion.div)`
|
||||
overflow: hidden;
|
||||
`
|
||||
Loading…
Reference in New Issue
Block a user