mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-08 22:39:36 +08:00
feat: code collapsible settings
This commit is contained in:
parent
d7cb80d8ca
commit
91c1f6169e
@ -113,6 +113,7 @@
|
|||||||
"settings.set_as_default": "Apply to default assistant",
|
"settings.set_as_default": "Apply to default assistant",
|
||||||
"settings.max": "Max",
|
"settings.max": "Max",
|
||||||
"settings.show_line_numbers": "Show Line Numbers in Code",
|
"settings.show_line_numbers": "Show Line Numbers in Code",
|
||||||
|
"settings.code_collapsible": "Code block collapsible",
|
||||||
"suggestions.title": "Suggested Questions",
|
"suggestions.title": "Suggested Questions",
|
||||||
"add.assistant.title": "Add Assistant",
|
"add.assistant.title": "Add Assistant",
|
||||||
"message.new.context": "New Context",
|
"message.new.context": "New Context",
|
||||||
|
|||||||
@ -113,6 +113,7 @@
|
|||||||
"settings.set_as_default": "Применить к ассистенту по умолчанию",
|
"settings.set_as_default": "Применить к ассистенту по умолчанию",
|
||||||
"settings.max": "Максимум",
|
"settings.max": "Максимум",
|
||||||
"settings.show_line_numbers": "Показать номера строк в коде",
|
"settings.show_line_numbers": "Показать номера строк в коде",
|
||||||
|
"settings.code_collapsible": "Блок кода свернут",
|
||||||
"suggestions.title": "Предложенные вопросы",
|
"suggestions.title": "Предложенные вопросы",
|
||||||
"add.assistant.title": "Добавить ассистента",
|
"add.assistant.title": "Добавить ассистента",
|
||||||
"message.new.context": "Новый контекст",
|
"message.new.context": "Новый контекст",
|
||||||
|
|||||||
@ -113,6 +113,7 @@
|
|||||||
"settings.set_as_default": "应用到默认助手",
|
"settings.set_as_default": "应用到默认助手",
|
||||||
"settings.max": "不限",
|
"settings.max": "不限",
|
||||||
"settings.show_line_numbers": "代码显示行号",
|
"settings.show_line_numbers": "代码显示行号",
|
||||||
|
"settings.code_collapsible": "代码块可折叠",
|
||||||
"suggestions.title": "建议的问题",
|
"suggestions.title": "建议的问题",
|
||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "清除上下文",
|
"message.new.context": "清除上下文",
|
||||||
|
|||||||
@ -113,6 +113,7 @@
|
|||||||
"settings.set_as_default": "設為預設助手",
|
"settings.set_as_default": "設為預設助手",
|
||||||
"settings.max": "最大",
|
"settings.max": "最大",
|
||||||
"settings.show_line_numbers": "代码顯示行號",
|
"settings.show_line_numbers": "代码顯示行號",
|
||||||
|
"settings.code_collapsible": "代码块可折叠",
|
||||||
"suggestions.title": "建議的問題",
|
"suggestions.title": "建議的問題",
|
||||||
"add.assistant.title": "添加助手",
|
"add.assistant.title": "添加助手",
|
||||||
"message.new.context": "新上下文",
|
"message.new.context": "新上下文",
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { CheckOutlined, DownOutlined, RightOutlined } from '@ant-design/icons'
|
|||||||
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
import CopyIcon from '@renderer/components/Icons/CopyIcon'
|
||||||
import { useSyntaxHighlighter } from '@renderer/context/SyntaxHighlighterProvider'
|
import { useSyntaxHighlighter } from '@renderer/context/SyntaxHighlighterProvider'
|
||||||
import { useSettings } from '@renderer/hooks/useSettings'
|
import { useSettings } from '@renderer/hooks/useSettings'
|
||||||
import React, { memo, useEffect, useState } from 'react'
|
import React, { memo, useEffect, useRef, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
@ -16,10 +16,10 @@ interface CodeBlockProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const CollapseIcon: React.FC<{ expanded: boolean; onClick: () => void }> = ({ expanded, onClick }) => {
|
const CollapseIcon: React.FC<{ expanded: boolean; onClick: () => void }> = ({ expanded, onClick }) => {
|
||||||
return expanded ? (
|
return (
|
||||||
<DownOutlined style={{ cursor: 'pointer' }} onClick={onClick} />
|
<CollapseIconWrapper onClick={onClick}>
|
||||||
) : (
|
{expanded ? <DownOutlined style={{ fontSize: 12 }} /> : <RightOutlined style={{ fontSize: 12 }} />}
|
||||||
<RightOutlined style={{ cursor: 'pointer' }} onClick={onClick} />
|
</CollapseIconWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,28 +31,22 @@ const ExpandButton: React.FC<{
|
|||||||
if (!showButton) return null
|
if (!showButton) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<ExpandButtonWrapper onClick={onClick}>
|
||||||
style={{
|
<div className="button-text">{isExpanded ? '收起' : '展开'}</div>
|
||||||
textAlign: 'center',
|
</ExpandButtonWrapper>
|
||||||
cursor: 'pointer',
|
|
||||||
padding: '8px',
|
|
||||||
color: 'var(--color-text-3)',
|
|
||||||
borderTop: '0.5px solid var(--color-code-background)'
|
|
||||||
}}
|
|
||||||
onClick={onClick}>
|
|
||||||
{isExpanded ? '收起' : '展开'}
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
||||||
const match = /language-(\w+)/.exec(className || '')
|
const match = /language-(\w+)/.exec(className || '')
|
||||||
const showFooterCopyButton = children && children.length > 500
|
const showFooterCopyButton = children && children.length > 500
|
||||||
const { codeShowLineNumbers, fontSize } = useSettings()
|
const { codeShowLineNumbers, fontSize, codeCollapsible } = useSettings()
|
||||||
const language = match?.[1] ?? 'text'
|
const language = match?.[1] ?? 'text'
|
||||||
const [html, setHtml] = useState<string>('')
|
const [html, setHtml] = useState<string>('')
|
||||||
const { codeToHtml } = useSyntaxHighlighter()
|
const { codeToHtml } = useSyntaxHighlighter()
|
||||||
const [isExpanded, setIsExpanded] = useState(false)
|
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
|
||||||
|
const [shouldShowExpandButton, setShouldShowExpandButton] = useState(false)
|
||||||
|
const codeContentRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadHighlightedCode = async () => {
|
const loadHighlightedCode = async () => {
|
||||||
@ -62,6 +56,24 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
loadHighlightedCode()
|
loadHighlightedCode()
|
||||||
}, [children, language, codeToHtml])
|
}, [children, language, codeToHtml])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (codeContentRef.current) {
|
||||||
|
setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350)
|
||||||
|
}
|
||||||
|
}, [html])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!codeCollapsible) {
|
||||||
|
setIsExpanded(true)
|
||||||
|
setShouldShowExpandButton(false)
|
||||||
|
} else {
|
||||||
|
setIsExpanded(!codeCollapsible)
|
||||||
|
if (codeContentRef.current) {
|
||||||
|
setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [codeCollapsible])
|
||||||
|
|
||||||
if (language === 'mermaid') {
|
if (language === 'mermaid') {
|
||||||
return <Mermaid chart={children} />
|
return <Mermaid chart={children} />
|
||||||
}
|
}
|
||||||
@ -70,12 +82,13 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
<div className="code-block">
|
<div className="code-block">
|
||||||
<CodeHeader>
|
<CodeHeader>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||||
<CollapseIcon expanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />
|
{codeCollapsible && <CollapseIcon expanded={isExpanded} onClick={() => setIsExpanded(!isExpanded)} />}
|
||||||
<CodeLanguage>{'<' + match[1].toUpperCase() + '>'}</CodeLanguage>
|
<CodeLanguage>{'<' + match[1].toUpperCase() + '>'}</CodeLanguage>
|
||||||
</div>
|
</div>
|
||||||
<CopyButton text={children} />
|
<CopyButton text={children} />
|
||||||
</CodeHeader>
|
</CodeHeader>
|
||||||
<CodeContent
|
<CodeContent
|
||||||
|
ref={codeContentRef}
|
||||||
isShowLineNumbers={codeShowLineNumbers}
|
isShowLineNumbers={codeShowLineNumbers}
|
||||||
dangerouslySetInnerHTML={{ __html: html }}
|
dangerouslySetInnerHTML={{ __html: html }}
|
||||||
style={{
|
style={{
|
||||||
@ -83,17 +96,19 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
|
|||||||
borderTopLeftRadius: 0,
|
borderTopLeftRadius: 0,
|
||||||
borderTopRightRadius: 0,
|
borderTopRightRadius: 0,
|
||||||
marginTop: 0,
|
marginTop: 0,
|
||||||
fontSize,
|
fontSize: fontSize - 1,
|
||||||
maxHeight: isExpanded ? 'none' : '300px',
|
maxHeight: codeCollapsible && !isExpanded ? '350px' : 'none',
|
||||||
overflow: 'hidden',
|
overflow: codeCollapsible && !isExpanded ? 'auto' : 'visible',
|
||||||
position: 'relative'
|
position: 'relative'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<ExpandButton
|
{codeCollapsible && (
|
||||||
isExpanded={isExpanded}
|
<ExpandButton
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
isExpanded={isExpanded}
|
||||||
showButton={!isExpanded || showFooterCopyButton}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
/>
|
showButton={shouldShowExpandButton}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{showFooterCopyButton && (
|
{showFooterCopyButton && (
|
||||||
<CodeFooter>
|
<CodeFooter>
|
||||||
<CopyButton text={children} style={{ marginTop: -40, marginRight: 10 }} />
|
<CopyButton text={children} style={{ marginTop: -40, marginRight: 10 }} />
|
||||||
@ -189,4 +204,45 @@ const CodeFooter = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const ExpandButtonWrapper = styled.div`
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 30px;
|
||||||
|
margin-top: -30px;
|
||||||
|
|
||||||
|
.button-text {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 8px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
z-index: 1;
|
||||||
|
transition: color 0.2s;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .button-text {
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const CollapseIconWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-background-soft);
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
export default memo(CodeBlock)
|
export default memo(CodeBlock)
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@r
|
|||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
setClickAssistantToShowTopic,
|
setClickAssistantToShowTopic,
|
||||||
|
setCodeCollapsible,
|
||||||
setCodeShowLineNumbers,
|
setCodeShowLineNumbers,
|
||||||
setCodeStyle,
|
setCodeStyle,
|
||||||
setFontSize,
|
setFontSize,
|
||||||
@ -39,6 +40,7 @@ const SettingsTab: FC = () => {
|
|||||||
pasteLongTextAsFile,
|
pasteLongTextAsFile,
|
||||||
renderInputMessageAsMarkdown,
|
renderInputMessageAsMarkdown,
|
||||||
codeShowLineNumbers,
|
codeShowLineNumbers,
|
||||||
|
codeCollapsible,
|
||||||
mathEngine,
|
mathEngine,
|
||||||
topicPosition,
|
topicPosition,
|
||||||
showTopicTime,
|
showTopicTime,
|
||||||
@ -81,6 +83,11 @@ const SettingsTab: FC = () => {
|
|||||||
/>
|
/>
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
<SettingDivider />
|
<SettingDivider />
|
||||||
|
<SettingRow>
|
||||||
|
<SettingRowTitleSmall>{t('chat.settings.code_collapsible')}</SettingRowTitleSmall>
|
||||||
|
<Switch size="small" checked={codeCollapsible} onChange={(checked) => dispatch(setCodeCollapsible(checked))} />
|
||||||
|
</SettingRow>
|
||||||
|
<SettingDivider />
|
||||||
<SettingRow>
|
<SettingRow>
|
||||||
<SettingRowTitleSmall>{t('message.message.style')}</SettingRowTitleSmall>
|
<SettingRowTitleSmall>{t('message.message.style')}</SettingRowTitleSmall>
|
||||||
<Select
|
<Select
|
||||||
|
|||||||
@ -25,6 +25,7 @@ export interface SettingsState {
|
|||||||
manualUpdateCheck: boolean
|
manualUpdateCheck: boolean
|
||||||
renderInputMessageAsMarkdown: boolean
|
renderInputMessageAsMarkdown: boolean
|
||||||
codeShowLineNumbers: boolean
|
codeShowLineNumbers: boolean
|
||||||
|
codeCollapsible: boolean
|
||||||
mathEngine: 'MathJax' | 'KaTeX'
|
mathEngine: 'MathJax' | 'KaTeX'
|
||||||
messageStyle: 'plain' | 'bubble'
|
messageStyle: 'plain' | 'bubble'
|
||||||
codeStyle: CodeStyleVarious
|
codeStyle: CodeStyleVarious
|
||||||
@ -57,6 +58,7 @@ const initialState: SettingsState = {
|
|||||||
manualUpdateCheck: false,
|
manualUpdateCheck: false,
|
||||||
renderInputMessageAsMarkdown: true,
|
renderInputMessageAsMarkdown: true,
|
||||||
codeShowLineNumbers: false,
|
codeShowLineNumbers: false,
|
||||||
|
codeCollapsible: false,
|
||||||
mathEngine: 'MathJax',
|
mathEngine: 'MathJax',
|
||||||
messageStyle: 'plain',
|
messageStyle: 'plain',
|
||||||
codeStyle: 'auto',
|
codeStyle: 'auto',
|
||||||
@ -128,6 +130,9 @@ const settingsSlice = createSlice({
|
|||||||
setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => {
|
setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => {
|
||||||
state.pasteLongTextAsFile = action.payload
|
state.pasteLongTextAsFile = action.payload
|
||||||
},
|
},
|
||||||
|
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.renderInputMessageAsMarkdown = action.payload
|
||||||
|
},
|
||||||
setClickAssistantToShowTopic: (state, action: PayloadAction<boolean>) => {
|
setClickAssistantToShowTopic: (state, action: PayloadAction<boolean>) => {
|
||||||
state.clickAssistantToShowTopic = action.payload
|
state.clickAssistantToShowTopic = action.payload
|
||||||
},
|
},
|
||||||
@ -146,12 +151,12 @@ const settingsSlice = createSlice({
|
|||||||
setWebdavPath: (state, action: PayloadAction<string>) => {
|
setWebdavPath: (state, action: PayloadAction<string>) => {
|
||||||
state.webdavPath = action.payload
|
state.webdavPath = action.payload
|
||||||
},
|
},
|
||||||
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
|
|
||||||
state.renderInputMessageAsMarkdown = action.payload
|
|
||||||
},
|
|
||||||
setCodeShowLineNumbers: (state, action: PayloadAction<boolean>) => {
|
setCodeShowLineNumbers: (state, action: PayloadAction<boolean>) => {
|
||||||
state.codeShowLineNumbers = action.payload
|
state.codeShowLineNumbers = action.payload
|
||||||
},
|
},
|
||||||
|
setCodeCollapsible: (state, action: PayloadAction<boolean>) => {
|
||||||
|
state.codeCollapsible = action.payload
|
||||||
|
},
|
||||||
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
|
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
|
||||||
state.mathEngine = action.payload
|
state.mathEngine = action.payload
|
||||||
},
|
},
|
||||||
@ -192,6 +197,7 @@ export const {
|
|||||||
setWebdavPass,
|
setWebdavPass,
|
||||||
setWebdavPath,
|
setWebdavPath,
|
||||||
setCodeShowLineNumbers,
|
setCodeShowLineNumbers,
|
||||||
|
setCodeCollapsible,
|
||||||
setMathEngine,
|
setMathEngine,
|
||||||
setMessageStyle,
|
setMessageStyle,
|
||||||
setCodeStyle
|
setCodeStyle
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user