mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-29 23:12:38 +08:00
Merge branch 'develop' into mutiple-select
This commit is contained in:
commit
ada130b907
@ -764,13 +764,13 @@
|
||||
"type": {
|
||||
"embedding": "嵌入",
|
||||
"free": "免费",
|
||||
"function_calling": "工具",
|
||||
"reasoning": "推理",
|
||||
"rerank": "重排",
|
||||
"select": "选择模型类型",
|
||||
"text": "文本",
|
||||
"vision": "图像",
|
||||
"function_calling": "函数调用",
|
||||
"websearch": "[to be translated]:WebSearch"
|
||||
"vision": "视觉",
|
||||
"websearch": "联网"
|
||||
}
|
||||
},
|
||||
"navbar": {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { ArrowsAltOutlined, ShrinkOutlined } from '@ant-design/icons'
|
||||
import ModelAvatar from '@renderer/components/Avatar/ModelAvatar'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
import Scrollbar from '@renderer/components/Scrollbar'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { useAppDispatch } from '@renderer/store'
|
||||
@ -7,7 +8,7 @@ import { setFoldDisplayMode } from '@renderer/store/settings'
|
||||
import type { Model } from '@renderer/types'
|
||||
import type { Message } from '@renderer/types/newMessage'
|
||||
import { Avatar, Segmented as AntdSegmented, Tooltip } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { FC, memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -25,39 +26,54 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
|
||||
const { foldDisplayMode } = useSettings()
|
||||
const isCompact = foldDisplayMode === 'compact'
|
||||
|
||||
const renderLabel = useCallback(
|
||||
(message: Message) => {
|
||||
const modelTip = message.model?.name
|
||||
|
||||
if (isCompact) {
|
||||
return (
|
||||
<Tooltip key={message.id} title={modelTip} mouseEnterDelay={0.5}>
|
||||
<AvatarWrapper
|
||||
className="avatar-wrapper"
|
||||
$isSelected={message.id === selectMessageId}
|
||||
onClick={() => {
|
||||
setSelectedMessage(message)
|
||||
}}>
|
||||
<ModelAvatar model={message.model as Model} size={22} />
|
||||
</AvatarWrapper>
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<SegmentedLabel>
|
||||
<ModelAvatar model={message.model as Model} size={20} />
|
||||
<ModelName>{message.model?.name}</ModelName>
|
||||
</SegmentedLabel>
|
||||
)
|
||||
},
|
||||
[isCompact, selectMessageId, setSelectedMessage]
|
||||
)
|
||||
|
||||
return (
|
||||
<ModelsWrapper>
|
||||
<Container>
|
||||
<DisplayModeToggle
|
||||
displayMode={foldDisplayMode}
|
||||
onClick={() => dispatch(setFoldDisplayMode(isCompact ? 'expanded' : 'compact'))}>
|
||||
<Tooltip
|
||||
title={
|
||||
foldDisplayMode === 'compact'
|
||||
isCompact
|
||||
? t(`message.message.multi_model_style.fold.expand`)
|
||||
: t('message.message.multi_model_style.fold.compress')
|
||||
}
|
||||
placement="top">
|
||||
{foldDisplayMode === 'compact' ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
|
||||
{isCompact ? <ArrowsAltOutlined /> : <ShrinkOutlined />}
|
||||
</Tooltip>
|
||||
</DisplayModeToggle>
|
||||
|
||||
<ModelsContainer $displayMode={foldDisplayMode}>
|
||||
{foldDisplayMode === 'compact' ? (
|
||||
{isCompact ? (
|
||||
/* Compact style display */
|
||||
<Avatar.Group className="avatar-group">
|
||||
{messages.map((message, index) => (
|
||||
<Tooltip key={index} title={message.model?.name} placement="top" mouseEnterDelay={0.2}>
|
||||
<AvatarWrapper
|
||||
className="avatar-wrapper"
|
||||
isSelected={message.id === selectMessageId}
|
||||
onClick={() => {
|
||||
setSelectedMessage(message)
|
||||
}}>
|
||||
<ModelAvatar model={message.model as Model} size={28} />
|
||||
</AvatarWrapper>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Avatar.Group>
|
||||
<Avatar.Group className="avatar-group">{messages.map((message) => renderLabel(message))}</Avatar.Group>
|
||||
) : (
|
||||
/* Expanded style display */
|
||||
<Segmented
|
||||
@ -67,45 +83,32 @@ const MessageGroupModelList: FC<MessageGroupModelListProps> = ({ messages, selec
|
||||
setSelectedMessage(message)
|
||||
}}
|
||||
options={messages.map((message) => ({
|
||||
label: (
|
||||
<SegmentedLabel>
|
||||
<ModelAvatar model={message.model as Model} size={20} />
|
||||
<ModelName>{message.model?.name}</ModelName>
|
||||
</SegmentedLabel>
|
||||
),
|
||||
label: renderLabel(message),
|
||||
value: message.id
|
||||
}))}
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</ModelsContainer>
|
||||
</ModelsWrapper>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const ModelsWrapper = styled.div`
|
||||
position: relative;
|
||||
display: flex;
|
||||
const Container = styled(HStack)`
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
margin-left: 4px;
|
||||
`
|
||||
|
||||
const DisplayModeToggle = styled.div<{ displayMode: DisplayMode }>`
|
||||
position: absolute;
|
||||
left: 4px; /* Add more space on the left */
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 5;
|
||||
width: 28px; /* Increase width */
|
||||
height: 28px; /* Add height */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 2px 6px 3px 6px;
|
||||
border-radius: 4px;
|
||||
padding: 2px;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
|
||||
/* Add hover effect */
|
||||
&:hover {
|
||||
background-color: var(--color-hover);
|
||||
}
|
||||
@ -119,9 +122,7 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
|
||||
overflow-x: auto;
|
||||
flex: 1;
|
||||
padding: 0 8px;
|
||||
margin-left: 24px; /* Space for toggle button */
|
||||
|
||||
/* Hide scrollbar to match original code */
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
@ -131,27 +132,23 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
position: relative;
|
||||
padding: 6px 4px;
|
||||
|
||||
/* Base style - default overlapping effect */
|
||||
& > * {
|
||||
margin-left: -6px !important;
|
||||
/* Separate transition properties to avoid conflicts */
|
||||
transition:
|
||||
transform 0.18s ease-out,
|
||||
margin 0.18s ease-out !important;
|
||||
position: relative;
|
||||
/* Only use will-change for transform to reduce rendering overhead */
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
/* First element has no left margin */
|
||||
& > *:first-child {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
/* Using :has() selector to handle the element before the hovered one */
|
||||
/* Element before the hovered one */
|
||||
& > *:has(+ *:hover) {
|
||||
margin-right: 2px !important;
|
||||
/* Use transform instead of margin to reduce layout recalculations */
|
||||
@ -171,52 +168,24 @@ const ModelsContainer = styled(Scrollbar)<{ $displayMode: DisplayMode }>`
|
||||
}
|
||||
`
|
||||
|
||||
const AvatarWrapper = styled.div<{ isSelected: boolean }>`
|
||||
const AvatarWrapper = styled.div<{ $isSelected: boolean }>`
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
border-radius: 50%;
|
||||
/* Keep z-index separate from transitions to avoid rendering issues */
|
||||
z-index: ${(props) => (props.isSelected ? 2 : 0)};
|
||||
background: var(--color-background);
|
||||
/* Simplify transitions to reduce jittering */
|
||||
transition:
|
||||
transform 0.18s ease-out,
|
||||
margin 0.18s ease-out,
|
||||
box-shadow 0.18s ease-out,
|
||||
filter 0.18s ease-out;
|
||||
box-shadow: 0 0 0 1px var(--color-background);
|
||||
|
||||
/* Use CSS variables to define animation parameters for easy adjustment */
|
||||
--hover-scale: 1.15;
|
||||
--hover-x-offset: 6px;
|
||||
--hover-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
z-index: ${(props) => (props.$isSelected ? 1 : 0)};
|
||||
border: ${(props) => (props.$isSelected ? '2px solid var(--color-primary)' : 'none')};
|
||||
|
||||
&:hover {
|
||||
/* z-index is applied immediately, not part of the transition */
|
||||
z-index: 10;
|
||||
transform: translateX(var(--hover-x-offset)) scale(var(--hover-scale));
|
||||
box-shadow: var(--hover-shadow);
|
||||
transform: translateX(6px) scale(1.15);
|
||||
filter: brightness(1.02);
|
||||
margin-left: 8px !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.isSelected &&
|
||||
`
|
||||
border: 2px solid var(--color-primary);
|
||||
z-index: 2;
|
||||
|
||||
&:hover {
|
||||
/* z-index is applied immediately, not part of the transition */
|
||||
z-index: 10;
|
||||
border: 2px solid var(--color-primary);
|
||||
filter: brightness(1.02);
|
||||
transform: translateX(var(--hover-x-offset)) scale(var(--hover-scale));
|
||||
margin-left: 8px !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
`}
|
||||
`
|
||||
|
||||
const Segmented = styled(AntdSegmented)`
|
||||
@ -224,21 +193,15 @@ const Segmented = styled(AntdSegmented)`
|
||||
background-color: transparent !important;
|
||||
|
||||
.ant-segmented-item {
|
||||
background-color: transparent !important;
|
||||
transition: none !important;
|
||||
border-radius: var(--list-item-border-radius) !important;
|
||||
box-shadow: none !important;
|
||||
&:hover {
|
||||
background: transparent !important;
|
||||
}
|
||||
}
|
||||
.ant-segmented-thumb,
|
||||
.ant-segmented-item-selected {
|
||||
background-color: transparent !important;
|
||||
border: 0.5px solid var(--color-border);
|
||||
transition: none !important;
|
||||
border-radius: var(--list-item-border-radius) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
`
|
||||
|
||||
@ -254,4 +217,4 @@ const ModelName = styled.span`
|
||||
font-size: 12px;
|
||||
`
|
||||
|
||||
export default MessageGroupModelList
|
||||
export default memo(MessageGroupModelList)
|
||||
|
||||
@ -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,
|
||||
@ -181,478 +182,488 @@ 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>
|
||||
<SettingDivider />
|
||||
<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 />
|
||||
<SettingRow>
|
||||
<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 })
|
||||
}}
|
||||
/>
|
||||
</SettingRow>
|
||||
{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>
|
||||
)}
|
||||
<SettingDivider />
|
||||
</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>
|
||||
</>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<SettingDivider />
|
||||
</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>
|
||||
<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>
|
||||
</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 +688,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;
|
||||
|
||||
61
src/renderer/src/pages/settings/SettingGroup.tsx
Normal file
61
src/renderer/src/pages/settings/SettingGroup.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
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;
|
||||
`
|
||||
|
||||
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;
|
||||
`
|
||||
@ -215,10 +215,9 @@ export async function getMessageTitle(message: Message, length = 30): Promise<st
|
||||
try {
|
||||
window.message.loading({ content: t('chat.topics.export.wait_for_title_naming'), key: 'message-title-naming' })
|
||||
|
||||
const tempTextBlock = createMainTextBlock(message.id, content, { status: MessageBlockStatus.SUCCESS })
|
||||
const tempMessage = resetMessage(message, {
|
||||
status: AssistantMessageStatus.SUCCESS,
|
||||
blocks: [tempTextBlock.id]
|
||||
blocks: message.blocks,
|
||||
})
|
||||
|
||||
const title = await fetchMessagesSummary({ messages: [tempMessage], assistant: {} as Assistant })
|
||||
|
||||
@ -805,21 +805,19 @@ export const deleteMessageGroupThunk =
|
||||
const currentState = getState()
|
||||
const topicMessageIds = currentState.messages.messageIdsByTopic[topicId] || []
|
||||
const messagesToDelete: Message[] = []
|
||||
const idsToDelete: string[] = []
|
||||
|
||||
topicMessageIds.forEach((id) => {
|
||||
const msg = currentState.messages.entities[id]
|
||||
if (msg && msg.askId === askId) {
|
||||
messagesToDelete.push(msg)
|
||||
idsToDelete.push(id)
|
||||
}
|
||||
})
|
||||
|
||||
const userQuery = currentState.messages.entities[askId]
|
||||
if (userQuery && userQuery.topicId === topicId && !idsToDelete.includes(askId)) {
|
||||
messagesToDelete.push(userQuery)
|
||||
idsToDelete.push(askId)
|
||||
}
|
||||
// const userQuery = currentState.messages.entities[askId]
|
||||
// if (userQuery && userQuery.topicId === topicId && !idsToDelete.includes(askId)) {
|
||||
// messagesToDelete.push(userQuery)
|
||||
// idsToDelete.push(askId)
|
||||
// }
|
||||
|
||||
if (messagesToDelete.length === 0) {
|
||||
console.warn(`[deleteMessageGroup] No messages found with askId ${askId} in topic ${topicId}.`)
|
||||
@ -894,13 +892,29 @@ export const resendMessageThunk =
|
||||
const resetDataList: Message[] = []
|
||||
|
||||
if (assistantMessagesToReset.length === 0) {
|
||||
// 没有用户消息,就创建一个
|
||||
const assistantMessage = createAssistantMessage(assistant.id, topicId, {
|
||||
askId: userMessageToResend.id,
|
||||
model: assistant.model
|
||||
// 没有用户消息,就创建一个或多个
|
||||
|
||||
if (userMessageToResend?.mentions?.length) {
|
||||
console.log('userMessageToResend.mentions', userMessageToResend.mentions)
|
||||
for (const mention of userMessageToResend.mentions) {
|
||||
const assistantMessage = createAssistantMessage(assistant.id, topicId, {
|
||||
askId: userMessageToResend.id,
|
||||
model: mention,
|
||||
modelId: mention.id
|
||||
})
|
||||
resetDataList.push(assistantMessage)
|
||||
}
|
||||
} else {
|
||||
const assistantMessage = createAssistantMessage(assistant.id, topicId, {
|
||||
askId: userMessageToResend.id,
|
||||
model: assistant.model
|
||||
})
|
||||
resetDataList.push(assistantMessage)
|
||||
}
|
||||
|
||||
resetDataList.forEach((message) => {
|
||||
dispatch(newMessagesActions.addMessage({ topicId, message }))
|
||||
})
|
||||
resetDataList.push(assistantMessage)
|
||||
dispatch(newMessagesActions.addMessage({ topicId, message: assistantMessage }))
|
||||
}
|
||||
|
||||
const allBlockIdsToDelete: string[] = []
|
||||
|
||||
Loading…
Reference in New Issue
Block a user