feat: add React Developer Tools extension support and optimize CodeBlock component
This commit is contained in:
parent
32e1f428e7
commit
750247aef8
@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 }))
|
||||||
|
|||||||
@ -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} />
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user