feat: add svg preview

This commit is contained in:
kangfenmao 2024-12-16 12:35:39 +08:00
parent 96737ed695
commit 0074d5c8b4
5 changed files with 50 additions and 13 deletions

View File

@ -94,14 +94,8 @@
background-color: var(--color-border);
}
.katex-display {
span {
white-space: pre;
}
}
span {
white-space: pre-wrap;
white-space: pre;
}
p code,

View File

@ -8,6 +8,7 @@ import styled from 'styled-components'
import Artifacts from './Artifacts'
import Mermaid from './Mermaid'
import SvgPreview from './SvgPreview'
interface CodeBlockProps {
children: string
@ -79,8 +80,20 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
return <Mermaid chart={children} />
}
if (language === 'svg') {
return (
<CodeBlockWrapper className="code-block">
<CodeHeader>
<CodeLanguage>{'<SVG>'}</CodeLanguage>
<CopyButton text={children} />
</CodeHeader>
<SvgPreview>{children}</SvgPreview>
</CodeBlockWrapper>
)
}
return match ? (
<div className="code-block">
<CodeBlockWrapper className="code-block">
<CodeHeader>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
{codeCollapsible && shouldShowExpandButton && (
@ -118,7 +131,7 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
</CodeFooter>
)}
{language === 'html' && children?.includes('</html>') && <Artifacts html={children} />}
</div>
</CodeBlockWrapper>
) : (
<code className={className}>{children}</code>
)
@ -142,6 +155,8 @@ const CopyButton: React.FC<{ text: string; style?: React.CSSProperties }> = ({ t
)
}
const CodeBlockWrapper = styled.div``
const CodeContent = styled.div<{ isShowLineNumbers: boolean }>`
.shiki {
padding: 1em;

View File

@ -2,7 +2,7 @@ import 'katex/dist/katex.min.css'
import { useSettings } from '@renderer/hooks/useSettings'
import { Message } from '@renderer/types'
import { escapeBrackets } from '@renderer/utils/formula'
import { escapeBrackets, removeSvgEmptyLines } from '@renderer/utils/formula'
import { isEmpty } from 'lodash'
import { FC, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
@ -19,7 +19,7 @@ import ImagePreview from './ImagePreview'
import Link from './Link'
const ALLOWED_ELEMENTS =
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr)/i
/<(style|p|div|span|b|i|strong|em|ul|ol|li|table|tr|td|th|thead|tbody|h[1-6]|blockquote|pre|code|br|hr|svg|path|circle|rect|line|polyline|polygon|text|g|defs|title|desc|tspan)/i
interface Props {
message: Message
@ -35,7 +35,7 @@ const Markdown: FC<Props> = ({ message }) => {
const empty = isEmpty(message.content)
const paused = message.status === 'paused'
const content = empty && paused ? t('message.chat.completion.paused') : message.content
return escapeBrackets(content)
return removeSvgEmptyLines(escapeBrackets(content))
}, [message.content, message.status, t])
const rehypePlugins = useMemo(() => {
@ -52,7 +52,6 @@ const Markdown: FC<Props> = ({ message }) => {
className="markdown"
rehypePlugins={rehypePlugins}
remarkPlugins={[remarkMath, remarkGfm]}
disallowedElements={mathEngine === 'KaTeX' ? ['style'] : []}
components={
{
a: Link,

View File

@ -0,0 +1,16 @@
const SvgPreview = ({ children }: { children: string }) => {
return (
<div
dangerouslySetInnerHTML={{ __html: children }}
style={{
padding: '1em',
backgroundColor: 'white',
border: '0.5px solid var(--color-code-background)',
borderTopLeftRadius: 0,
borderTopRightRadius: 0
}}
/>
)
}
export default SvgPreview

View File

@ -43,3 +43,16 @@ export function extractTitle(html: string): string | null {
return null
}
export function removeSvgEmptyLines(text: string): string {
// 用正则表达式匹配 <svg> 标签内的内容
const svgPattern = /(<svg[\s\S]*?<\/svg>)/g
return text.replace(svgPattern, (svgMatch) => {
// 将 SVG 内容按行分割,过滤掉空行,然后重新组合
return svgMatch
.split('\n')
.filter((line) => line.trim() !== '')
.join('\n')
})
}