feat: add React Developer Tools extension support and optimize CodeBlock component

This commit is contained in:
MyPrototypeWhat 2025-04-01 00:55:31 +08:00 committed by 亢奋猫
parent 32e1f428e7
commit 750247aef8
4 changed files with 24 additions and 19 deletions

View File

@ -1,7 +1,7 @@
import { electronApp, optimizer } from '@electron-toolkit/utils' import { electronApp, optimizer } from '@electron-toolkit/utils'
import { replaceDevtoolsFont } from '@main/utils/windowUtil' import { replaceDevtoolsFont } from '@main/utils/windowUtil'
import { app, ipcMain } from 'electron' import { app, ipcMain } from 'electron'
import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer' import installExtension, { REACT_DEVELOPER_TOOLS, REDUX_DEVTOOLS } from 'electron-devtools-installer'
import { registerIpc } from './ipc' import { registerIpc } from './ipc'
import { configManager } from './services/ConfigManager' import { configManager } from './services/ConfigManager'
@ -48,7 +48,7 @@ if (!app.requestSingleInstanceLock()) {
replaceDevtoolsFont(mainWindow) replaceDevtoolsFont(mainWindow)
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {
installExtension(REDUX_DEVTOOLS) installExtension([REDUX_DEVTOOLS, REACT_DEVELOPER_TOOLS])
.then((name) => console.log(`Added Extension: ${name}`)) .then((name) => console.log(`Added Extension: ${name}`))
.catch((err) => console.log('An error occurred: ', err)) .catch((err) => console.log('An error occurred: ', err))
} }

View File

@ -17,6 +17,7 @@ import {
} from '@renderer/store/assistants' } from '@renderer/store/assistants'
import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm' import { setDefaultModel, setTopicNamingModel, setTranslateModel } from '@renderer/store/llm'
import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types' import { Assistant, AssistantSettings, Model, Topic } from '@renderer/types'
import { useCallback } from 'react'
import { TopicManager } from './useTopic' import { TopicManager } from './useTopic'
@ -69,7 +70,10 @@ export function useAssistant(id: string) {
updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })), updateTopic: (topic: Topic) => dispatch(updateTopic({ assistantId: assistant.id, topic })),
updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })), updateTopics: (topics: Topic[]) => dispatch(updateTopics({ assistantId: assistant.id, topics })),
removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })), removeAllTopics: () => dispatch(removeAllTopics({ assistantId: assistant.id })),
setModel: (model: Model) => dispatch(setModel({ assistantId: assistant.id, model })), setModel: useCallback(
(model: Model) => dispatch(setModel({ assistantId: assistant.id, model })),
[dispatch, assistant.id]
),
updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)), updateAssistant: (assistant: Assistant) => dispatch(updateAssistant(assistant)),
updateAssistantSettings: (settings: Partial<AssistantSettings>) => { updateAssistantSettings: (settings: Partial<AssistantSettings>) => {
dispatch(updateAssistantSettings({ assistantId: assistant.id, settings })) dispatch(updateAssistantSettings({ assistantId: assistant.id, settings }))

View File

@ -37,12 +37,17 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
const showDownloadButton = ['csv', 'json', 'txt', 'md'].includes(language) const showDownloadButton = ['csv', 'json', 'txt', 'md'].includes(language)
const shouldShowExpandButtonRef = useRef(false)
useEffect(() => { useEffect(() => {
const loadHighlightedCode = async () => { const loadHighlightedCode = async () => {
const highlightedHtml = await codeToHtml(children, language) const highlightedHtml = await codeToHtml(children, language)
if (codeContentRef.current) { if (codeContentRef.current) {
codeContentRef.current.innerHTML = highlightedHtml codeContentRef.current.innerHTML = highlightedHtml
setShouldShowExpandButton(codeContentRef.current.scrollHeight > 350) const isShowExpandButton = codeContentRef.current.scrollHeight > 350
if (shouldShowExpandButtonRef.current === isShowExpandButton) return
shouldShowExpandButtonRef.current = isShowExpandButton
setShouldShowExpandButton(shouldShowExpandButtonRef.current)
} }
} }
loadHighlightedCode() loadHighlightedCode()
@ -98,15 +103,13 @@ const CodeBlock: React.FC<CodeBlockProps> = ({ children, className }) => {
)} )}
<CodeLanguage>{'<' + language.toUpperCase() + '>'}</CodeLanguage> <CodeLanguage>{'<' + language.toUpperCase() + '>'}</CodeLanguage>
</div> </div>
</CodeHeader> </CodeHeader>
<StickyWrapper> <StickyWrapper>
<HStack <HStack
position="absolute" position="absolute"
gap={12} gap={12}
alignItems="center" alignItems="center"
style={{ bottom: '0.2rem', right: '1rem', height: "27px" }} style={{ bottom: '0.2rem', right: '1rem', height: '27px' }}>
>
{showDownloadButton && <DownloadButton language={language} data={children} />} {showDownloadButton && <DownloadButton language={language} data={children} />}
{codeWrappable && <UnwrapButton unwrapped={isUnwrapped} onClick={() => setIsUnwrapped(!isUnwrapped)} />} {codeWrappable && <UnwrapButton unwrapped={isUnwrapped} onClick={() => setIsUnwrapped(!isUnwrapped)} />}
<CopyButton text={children} /> <CopyButton text={children} />

View File

@ -7,7 +7,7 @@ import { useSettings } from '@renderer/hooks/useSettings'
import type { Message } from '@renderer/types' import type { Message } from '@renderer/types'
import { escapeBrackets, removeSvgEmptyLines, withGeminiGrounding } from '@renderer/utils/formats' import { escapeBrackets, removeSvgEmptyLines, withGeminiGrounding } from '@renderer/utils/formats'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { type FC, useCallback, useMemo } from 'react' import { type FC, useMemo } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import ReactMarkdown, { type Components } from 'react-markdown' import ReactMarkdown, { type Components } from 'react-markdown'
import rehypeKatex from 'rehype-katex' import rehypeKatex from 'rehype-katex'
@ -37,6 +37,8 @@ interface Props {
> >
} }
const remarkPlugins = [remarkMath, remarkGfm, remarkCjkFriendly]
const disallowedElements = ['iframe']
const Markdown: FC<Props> = ({ message, citationsData }) => { const Markdown: FC<Props> = ({ message, citationsData }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { renderInputMessageAsMarkdown, mathEngine } = useSettings() const { renderInputMessageAsMarkdown, mathEngine } = useSettings()
@ -55,7 +57,7 @@ const Markdown: FC<Props> = ({ message, citationsData }) => {
return hasElements ? [rehypeRaw, rehypeMath] : [rehypeMath] return hasElements ? [rehypeRaw, rehypeMath] : [rehypeMath]
}, [messageContent, rehypeMath]) }, [messageContent, rehypeMath])
const components = useCallback(() => { const components = useMemo(() => {
const baseComponents = { const baseComponents = {
a: (props: any) => { a: (props: any) => {
if (props.href && citationsData?.has(props.href)) { if (props.href && citationsData?.has(props.href)) {
@ -65,15 +67,11 @@ const Markdown: FC<Props> = ({ message, citationsData }) => {
}, },
code: CodeBlock, code: CodeBlock,
img: ImagePreview, img: ImagePreview,
pre: (props: any) => <pre style={{ overflow: 'visible' }} {...props} /> pre: (props: any) => <pre style={{ overflow: 'visible' }} {...props} />,
style: MarkdownShadowDOMRenderer as any
} as Partial<Components> } as Partial<Components>
if (messageContent.includes('<style>')) {
baseComponents.style = MarkdownShadowDOMRenderer as any
}
return baseComponents return baseComponents
}, [messageContent, citationsData]) }, [citationsData])
if (message.role === 'user' && !renderInputMessageAsMarkdown) { if (message.role === 'user' && !renderInputMessageAsMarkdown) {
return <p style={{ marginBottom: 5, whiteSpace: 'pre-wrap' }}>{messageContent}</p> return <p style={{ marginBottom: 5, whiteSpace: 'pre-wrap' }}>{messageContent}</p>
@ -82,10 +80,10 @@ const Markdown: FC<Props> = ({ message, citationsData }) => {
return ( return (
<ReactMarkdown <ReactMarkdown
rehypePlugins={rehypePlugins} rehypePlugins={rehypePlugins}
remarkPlugins={[remarkMath, remarkGfm, remarkCjkFriendly]} remarkPlugins={remarkPlugins}
className="markdown" className="markdown"
components={components()} components={components}
disallowedElements={['iframe']} disallowedElements={disallowedElements}
remarkRehypeOptions={{ remarkRehypeOptions={{
footnoteLabel: t('common.footnotes'), footnoteLabel: t('common.footnotes'),
footnoteLabelTagName: 'h4', footnoteLabelTagName: 'h4',