mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 11:44:28 +08:00
feat(HtmlArtifacts): make fancy code block optional (#10043)
* feat(HtmlArtifacts): make fancy code block optional * test: fix mocks
This commit is contained in:
parent
f9c60423a8
commit
9f81a77943
@ -19,7 +19,7 @@ import { MAX_COLLAPSED_CODE_HEIGHT } from '@renderer/config/constant'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { pyodideService } from '@renderer/services/PyodideService'
|
||||
import { getExtensionByLanguage } from '@renderer/utils/code-language'
|
||||
import { extractHtmlTitle } from '@renderer/utils/formats'
|
||||
import { extractHtmlTitle, getFileNameFromHtmlTitle } from '@renderer/utils/formats'
|
||||
import dayjs from 'dayjs'
|
||||
import React, { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -135,8 +135,8 @@ export const CodeBlockView: React.FC<Props> = memo(({ children, language, onSave
|
||||
let fileName = ''
|
||||
|
||||
// 尝试提取 HTML 标题
|
||||
if (language === 'html' && children.includes('</html>')) {
|
||||
fileName = extractHtmlTitle(children) || ''
|
||||
if (language === 'html') {
|
||||
fileName = getFileNameFromHtmlTitle(extractHtmlTitle(children)) || ''
|
||||
}
|
||||
|
||||
// 默认使用日期格式命名
|
||||
|
||||
@ -538,6 +538,10 @@
|
||||
"tip": "The run button will be displayed in the toolbar of executable code blocks, please do not execute dangerous code!",
|
||||
"title": "Code Execution"
|
||||
},
|
||||
"code_fancy_block": {
|
||||
"label": "Fancy code block",
|
||||
"tip": "Enable fancy style for code block, e.g., html card"
|
||||
},
|
||||
"code_image_tools": {
|
||||
"label": "Enable preview tools",
|
||||
"tip": "Enable preview tools for images rendered from code blocks such as mermaid"
|
||||
|
||||
@ -538,6 +538,10 @@
|
||||
"tip": "可执行的代码块工具栏中会显示运行按钮,注意不要执行危险代码!",
|
||||
"title": "代码执行"
|
||||
},
|
||||
"code_fancy_block": {
|
||||
"label": "花式代码块",
|
||||
"tip": "使用更美观的代码块样式,例如 HTML 卡片"
|
||||
},
|
||||
"code_image_tools": {
|
||||
"label": "启用预览工具",
|
||||
"tip": "为 mermaid 等代码块渲染后的图像启用预览工具"
|
||||
|
||||
@ -538,6 +538,10 @@
|
||||
"tip": "可執行的程式碼塊工具欄中會顯示運行按鈕,注意不要執行危險程式碼!",
|
||||
"title": "程式碼執行"
|
||||
},
|
||||
"code_fancy_block": {
|
||||
"label": "花式程式碼區塊",
|
||||
"tip": "使用更美觀的程式碼區塊樣式,例如 HTML 卡片"
|
||||
},
|
||||
"code_image_tools": {
|
||||
"label": "啟用預覽工具",
|
||||
"tip": "為 mermaid 等程式碼區塊渲染後的圖像啟用預覽工具"
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { CodeBlockView, HtmlArtifactsCard } from '@renderer/components/CodeBlockView'
|
||||
import { useSettings } from '@renderer/hooks/useSettings'
|
||||
import { EVENT_NAMES, EventEmitter } from '@renderer/services/EventService'
|
||||
import store from '@renderer/store'
|
||||
import { messageBlocksSelectors } from '@renderer/store/messageBlock'
|
||||
@ -19,6 +20,7 @@ const CodeBlock: React.FC<Props> = ({ children, className, node, blockId }) => {
|
||||
const languageMatch = /language-([\w-+]+)/.exec(className || '')
|
||||
const isMultiline = children?.includes('\n')
|
||||
const language = languageMatch?.[1] ?? (isMultiline ? 'text' : null)
|
||||
const { codeFancyBlock } = useSettings()
|
||||
|
||||
// 代码块 id
|
||||
const id = useMemo(() => getCodeBlockId(node?.position?.start), [node?.position?.start])
|
||||
@ -41,10 +43,12 @@ const CodeBlock: React.FC<Props> = ({ children, className, node, blockId }) => {
|
||||
)
|
||||
|
||||
if (language !== null) {
|
||||
// HTML 代码块特殊处理
|
||||
if (language === 'html') {
|
||||
const isOpenFence = isOpenFenceBlock(children?.length, languageMatch?.[1]?.length, node?.position)
|
||||
return <HtmlArtifactsCard html={children} onSave={handleSave} isStreaming={isStreaming && isOpenFence} />
|
||||
// Fancy code block
|
||||
if (codeFancyBlock) {
|
||||
if (language === 'html') {
|
||||
const isOpenFence = isOpenFenceBlock(children?.length, languageMatch?.[1]?.length, node?.position)
|
||||
return <HtmlArtifactsCard html={children} onSave={handleSave} isStreaming={isStreaming && isOpenFence} />
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@ -12,6 +12,7 @@ const mocks = vi.hoisted(() => ({
|
||||
getCodeBlockId: vi.fn(),
|
||||
isOpenFenceBlock: vi.fn(),
|
||||
selectById: vi.fn(),
|
||||
useSettings: vi.fn().mockReturnValue({ codeFancyBlock: true }),
|
||||
CodeBlockView: vi.fn(({ onSave, children }) => (
|
||||
<div>
|
||||
<code>{children}</code>
|
||||
@ -53,6 +54,10 @@ vi.mock('@renderer/store/messageBlock', () => ({
|
||||
}
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/hooks/useSettings', () => ({
|
||||
useSettings: () => mocks.useSettings()
|
||||
}))
|
||||
|
||||
vi.mock('@renderer/components/CodeBlockView', () => ({
|
||||
CodeBlockView: mocks.CodeBlockView,
|
||||
HtmlArtifactsCard: mocks.HtmlArtifactsCard
|
||||
|
||||
@ -23,6 +23,7 @@ import {
|
||||
setCodeCollapsible,
|
||||
setCodeEditor,
|
||||
setCodeExecution,
|
||||
setCodeFancyBlock,
|
||||
setCodeImageTools,
|
||||
setCodeShowLineNumbers,
|
||||
setCodeViewer,
|
||||
@ -99,6 +100,7 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
codeViewer,
|
||||
codeImageTools,
|
||||
codeExecution,
|
||||
codeFancyBlock,
|
||||
mathEngine,
|
||||
mathEnableSingleDollar,
|
||||
autoTranslateWithSpace,
|
||||
@ -451,6 +453,18 @@ const SettingsTab: FC<Props> = (props) => {
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_fancy_block.label')}
|
||||
<HelpTooltip title={t('chat.settings.code_fancy_block.tip')} />
|
||||
</SettingRowTitleSmall>
|
||||
<Switch
|
||||
size="small"
|
||||
checked={codeFancyBlock}
|
||||
onChange={(checked) => dispatch(setCodeFancyBlock(checked))}
|
||||
/>
|
||||
</SettingRow>
|
||||
<SettingDivider />
|
||||
<SettingRow>
|
||||
<SettingRowTitleSmall>
|
||||
{t('chat.settings.code_execution.title')}
|
||||
|
||||
@ -2417,6 +2417,17 @@ const migrateConfig = {
|
||||
logger.error('migrate 150 error', error as Error)
|
||||
return state
|
||||
}
|
||||
},
|
||||
'151': (state: RootState) => {
|
||||
try {
|
||||
if (state.settings) {
|
||||
state.settings.codeFancyBlock = true
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
logger.error('migrate 151 error', error as Error)
|
||||
return state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -97,6 +97,7 @@ export interface SettingsState {
|
||||
codeCollapsible: boolean
|
||||
codeWrappable: boolean
|
||||
codeImageTools: boolean
|
||||
codeFancyBlock: boolean
|
||||
mathEngine: MathEngine
|
||||
mathEnableSingleDollar: boolean
|
||||
messageStyle: 'plain' | 'bubble'
|
||||
@ -282,6 +283,7 @@ export const initialState: SettingsState = {
|
||||
codeCollapsible: false,
|
||||
codeWrappable: false,
|
||||
codeImageTools: false,
|
||||
codeFancyBlock: true,
|
||||
mathEngine: 'KaTeX',
|
||||
mathEnableSingleDollar: true,
|
||||
messageStyle: 'plain',
|
||||
@ -611,6 +613,9 @@ const settingsSlice = createSlice({
|
||||
setCodeImageTools: (state, action: PayloadAction<boolean>) => {
|
||||
state.codeImageTools = action.payload
|
||||
},
|
||||
setCodeFancyBlock: (state, action: PayloadAction<boolean>) => {
|
||||
state.codeFancyBlock = action.payload
|
||||
},
|
||||
setMathEngine: (state, action: PayloadAction<MathEngine>) => {
|
||||
state.mathEngine = action.payload
|
||||
},
|
||||
@ -900,6 +905,7 @@ export const {
|
||||
setCodeCollapsible,
|
||||
setCodeWrappable,
|
||||
setCodeImageTools,
|
||||
setCodeFancyBlock,
|
||||
setMathEngine,
|
||||
setMathEnableSingleDollar,
|
||||
setFoldDisplayMode,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user