feat: code collapsible settings

This commit is contained in:
kangfenmao 2024-11-18 17:44:33 +08:00
parent 84fa5b065b
commit 5bffb86d4f
7 changed files with 103 additions and 30 deletions

View File

@ -113,6 +113,7 @@
"settings.set_as_default": "Apply to default assistant",
"settings.max": "Max",
"settings.show_line_numbers": "Show Line Numbers in Code",
"settings.code_collapsible": "Code block collapsible",
"suggestions.title": "Suggested Questions",
"add.assistant.title": "Add Assistant",
"message.new.context": "New Context",

View File

@ -113,6 +113,7 @@
"settings.set_as_default": "Применить к ассистенту по умолчанию",
"settings.max": "Максимум",
"settings.show_line_numbers": "Показать номера строк в коде",
"settings.code_collapsible": "Блок кода свернут",
"suggestions.title": "Предложенные вопросы",
"add.assistant.title": "Добавить ассистента",
"message.new.context": "Новый контекст",

View File

@ -113,6 +113,7 @@
"settings.set_as_default": "应用到默认助手",
"settings.max": "不限",
"settings.show_line_numbers": "代码显示行号",
"settings.code_collapsible": "代码块可折叠",
"suggestions.title": "建议的问题",
"add.assistant.title": "添加助手",
"message.new.context": "清除上下文",

View File

@ -113,6 +113,7 @@
"settings.set_as_default": "設為預設助手",
"settings.max": "最大",
"settings.show_line_numbers": "代码顯示行號",
"settings.code_collapsible": "代码块可折叠",
"suggestions.title": "建議的問題",
"add.assistant.title": "添加助手",
"message.new.context": "新上下文",

View File

@ -2,7 +2,7 @@ import { CheckOutlined, DownOutlined, RightOutlined } from '@ant-design/icons'
import CopyIcon from '@renderer/components/Icons/CopyIcon'
import { useSyntaxHighlighter } from '@renderer/context/SyntaxHighlighterProvider'
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 styled from 'styled-components'
@ -16,10 +16,10 @@ interface CodeBlockProps {
}
const CollapseIcon: React.FC<{ expanded: boolean; onClick: () => void }> = ({ expanded, onClick }) => {
return expanded ? (
<DownOutlined style={{ cursor: 'pointer' }} onClick={onClick} />
) : (
<RightOutlined style={{ cursor: 'pointer' }} onClick={onClick} />
return (
<CollapseIconWrapper onClick={onClick}>
{expanded ? <DownOutlined style={{ fontSize: 12 }} /> : <RightOutlined style={{ fontSize: 12 }} />}
</CollapseIconWrapper>
)
}
@ -31,28 +31,22 @@ const ExpandButton: React.FC<{
if (!showButton) return null
return (
<div
style={{
textAlign: 'center',
cursor: 'pointer',
padding: '8px',
color: 'var(--color-text-3)',
borderTop: '0.5px solid var(--color-code-background)'
}}
onClick={onClick}>
{isExpanded ? '收起' : '展开'}
</div>
<ExpandButtonWrapper onClick={onClick}>
<div className="button-text">{isExpanded ? '收起' : '展开'}</div>
</ExpandButtonWrapper>
)
}
const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
const match = /language-(\w+)/.exec(className || '')
const showFooterCopyButton = children && children.length > 500
const { codeShowLineNumbers, fontSize } = useSettings()
const { codeShowLineNumbers, fontSize, codeCollapsible } = useSettings()
const language = match?.[1] ?? 'text'
const [html, setHtml] = useState<string>('')
const { codeToHtml } = useSyntaxHighlighter()
const [isExpanded, setIsExpanded] = useState(false)
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
const [shouldShowExpandButton, setShouldShowExpandButton] = useState(false)
const codeContentRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const loadHighlightedCode = async () => {
@ -62,6 +56,24 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
loadHighlightedCode()
}, [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') {
return <Mermaid chart={children} />
}
@ -70,12 +82,13 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
<div className="code-block">
<CodeHeader>
<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>
</div>
<CopyButton text={children} />
</CodeHeader>
<CodeContent
ref={codeContentRef}
isShowLineNumbers={codeShowLineNumbers}
dangerouslySetInnerHTML={{ __html: html }}
style={{
@ -83,17 +96,19 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
marginTop: 0,
fontSize,
maxHeight: isExpanded ? 'none' : '300px',
overflow: 'hidden',
fontSize: fontSize - 1,
maxHeight: codeCollapsible && !isExpanded ? '350px' : 'none',
overflow: codeCollapsible && !isExpanded ? 'auto' : 'visible',
position: 'relative'
}}
/>
<ExpandButton
isExpanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)}
showButton={!isExpanded || showFooterCopyButton}
/>
{codeCollapsible && (
<ExpandButton
isExpanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)}
showButton={shouldShowExpandButton}
/>
)}
{showFooterCopyButton && (
<CodeFooter>
<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)

View File

@ -6,6 +6,7 @@ import { SettingDivider, SettingRow, SettingRowTitle, SettingSubtitle } from '@r
import { useAppDispatch } from '@renderer/store'
import {
setClickAssistantToShowTopic,
setCodeCollapsible,
setCodeShowLineNumbers,
setCodeStyle,
setFontSize,
@ -39,6 +40,7 @@ const SettingsTab: FC = () => {
pasteLongTextAsFile,
renderInputMessageAsMarkdown,
codeShowLineNumbers,
codeCollapsible,
mathEngine,
topicPosition,
showTopicTime,
@ -81,6 +83,11 @@ const SettingsTab: FC = () => {
/>
</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('message.message.style')}</SettingRowTitleSmall>
<Select

View File

@ -25,6 +25,7 @@ export interface SettingsState {
manualUpdateCheck: boolean
renderInputMessageAsMarkdown: boolean
codeShowLineNumbers: boolean
codeCollapsible: boolean
mathEngine: 'MathJax' | 'KaTeX'
messageStyle: 'plain' | 'bubble'
codeStyle: CodeStyleVarious
@ -57,6 +58,7 @@ const initialState: SettingsState = {
manualUpdateCheck: false,
renderInputMessageAsMarkdown: true,
codeShowLineNumbers: false,
codeCollapsible: false,
mathEngine: 'MathJax',
messageStyle: 'plain',
codeStyle: 'auto',
@ -128,6 +130,9 @@ const settingsSlice = createSlice({
setPasteLongTextAsFile: (state, action: PayloadAction<boolean>) => {
state.pasteLongTextAsFile = action.payload
},
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
state.renderInputMessageAsMarkdown = action.payload
},
setClickAssistantToShowTopic: (state, action: PayloadAction<boolean>) => {
state.clickAssistantToShowTopic = action.payload
},
@ -146,12 +151,12 @@ const settingsSlice = createSlice({
setWebdavPath: (state, action: PayloadAction<string>) => {
state.webdavPath = action.payload
},
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
state.renderInputMessageAsMarkdown = action.payload
},
setCodeShowLineNumbers: (state, action: PayloadAction<boolean>) => {
state.codeShowLineNumbers = action.payload
},
setCodeCollapsible: (state, action: PayloadAction<boolean>) => {
state.codeCollapsible = action.payload
},
setMathEngine: (state, action: PayloadAction<'MathJax' | 'KaTeX'>) => {
state.mathEngine = action.payload
},
@ -192,6 +197,7 @@ export const {
setWebdavPass,
setWebdavPath,
setCodeShowLineNumbers,
setCodeCollapsible,
setMathEngine,
setMessageStyle,
setCodeStyle