feat: code collapsible settings

This commit is contained in:
kangfenmao 2024-11-18 17:44:33 +08:00
parent d7cb80d8ca
commit 91c1f6169e
7 changed files with 103 additions and 30 deletions

View File

@ -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",

View File

@ -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": "Новый контекст",

View File

@ -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": "清除上下文",

View File

@ -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": "新上下文",

View File

@ -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)

View File

@ -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

View File

@ -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