refactor(CodeTool): use hook for codeblock tools rather than context (#6273)

* refactor(CodeTool): use hook for codeblock tools rather than context

* fix: codeblock overflow behaviour

* fix: CodePreview scrollbar

* refactor: move margin to CodeHeader

* refactor: add min-width to codeblock
This commit is contained in:
one 2025-05-25 18:01:27 +08:00 committed by GitHub
parent a7520169e6
commit 2f312d68a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 159 additions and 227 deletions

View File

@ -321,6 +321,7 @@ mjx-container {
.cm-lineWrapping * {
word-wrap: break-word;
white-space: pre-wrap;
}
}
}

View File

@ -18,7 +18,8 @@ body[theme-mode='light'] {
height: 6px;
}
::-webkit-scrollbar-track {
::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
background: transparent;
}
@ -30,7 +31,7 @@ body[theme-mode='light'] {
}
}
pre::-webkit-scrollbar-thumb {
pre:not(.shiki)::-webkit-scrollbar-thumb {
border-radius: 0;
background: rgba(0, 0, 0, 0.08);
&:hover {

View File

@ -1,4 +1,4 @@
import { TOOL_SPECS, useCodeToolbar } from '@renderer/components/CodeToolbar'
import { CodeTool, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import { uuid } from '@renderer/utils'
@ -12,6 +12,7 @@ import styled from 'styled-components'
interface CodePreviewProps {
children: string
language: string
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
}
/**
@ -20,7 +21,7 @@ interface CodePreviewProps {
* - shiki tokenizer
* - tokenizer
*/
const CodePreview = ({ children, language }: CodePreviewProps) => {
const CodePreview = ({ children, language, setTools }: CodePreviewProps) => {
const { codeShowLineNumbers, fontSize, codeCollapsible, codeWrappable } = useSettings()
const { activeShikiTheme, highlightCodeChunk, cleanupTokenizers } = useCodeStyle()
const [isExpanded, setIsExpanded] = useState(!codeCollapsible)
@ -35,7 +36,7 @@ const CodePreview = ({ children, language }: CodePreviewProps) => {
const { t } = useTranslation()
const { registerTool, removeTool } = useCodeToolbar()
const { registerTool, removeTool } = useCodeTool(setTools)
// 展开/折叠工具
useEffect(() => {
@ -171,14 +172,13 @@ const CodePreview = ({ children, language }: CodePreviewProps) => {
ref={codeContentRef}
$lineNumbers={codeShowLineNumbers}
$wrap={codeWrappable && !isUnwrapped}
$fadeIn={hasHighlightedCode}
style={{
fontSize: fontSize - 1,
maxHeight: codeCollapsible && !isExpanded ? '350px' : 'none'
}}>
{hasHighlightedCode ? (
<div className="fade-in-effect">
<ShikiTokensRenderer language={language} tokenLines={tokenLines} />
</div>
<ShikiTokensRenderer language={language} tokenLines={tokenLines} />
) : (
<CodePlaceholder>{children}</CodePlaceholder>
)}
@ -229,26 +229,22 @@ const ShikiTokensRenderer: React.FC<{ language: string; tokenLines: ThemedToken[
const ContentContainer = styled.div<{
$lineNumbers: boolean
$wrap: boolean
$fadeIn: boolean
}>`
display: block;
position: relative;
overflow: auto;
display: flex;
flex-direction: column;
border: 0.5px solid transparent;
border-radius: 5px;
margin-top: 0;
::-webkit-scrollbar-thumb {
border-radius: 10px;
}
.shiki {
display: inline-block;
min-width: 100%;
padding: 1em;
code {
display: flex;
flex-direction: column;
width: 100%;
display: block;
.line {
display: block;
@ -256,7 +252,7 @@ const ContentContainer = styled.div<{
padding-left: ${(props) => (props.$lineNumbers ? '2rem' : '0')};
* {
word-wrap: ${(props) => (props.$wrap ? 'break-word' : undefined)};
overflow-wrap: ${(props) => (props.$wrap ? 'break-word' : 'normal')};
white-space: ${(props) => (props.$wrap ? 'pre-wrap' : 'pre')};
}
}
@ -292,18 +288,15 @@ const ContentContainer = styled.div<{
}
}
.fade-in-effect {
animation: contentFadeIn 0.3s ease-in-out forwards;
}
animation: ${(props) => (props.$fadeIn ? 'contentFadeIn 0.3s ease-in-out forwards' : 'none')};
`
const CodePlaceholder = styled.div`
display: block;
opacity: 0.1;
flex-direction: column;
white-space: pre-wrap;
word-break: break-all;
overflow-x: hidden;
display: block;
min-height: 1.3rem;
`

View File

@ -1,5 +1,5 @@
import { nanoid } from '@reduxjs/toolkit'
import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { useMermaid } from '@renderer/hooks/useMermaid'
import { Flex } from 'antd'
import React, { memo, startTransition, useCallback, useEffect, useRef, useState } from 'react'
@ -7,9 +7,10 @@ import styled from 'styled-components'
interface Props {
children: string
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
}
const MermaidPreview: React.FC<Props> = ({ children }) => {
const MermaidPreview: React.FC<Props> = ({ children, setTools }) => {
const { mermaid, isLoading, error: mermaidError } = useMermaid()
const mermaidRef = useRef<HTMLDivElement>(null)
const [error, setError] = useState<string | null>(null)
@ -25,6 +26,7 @@ const MermaidPreview: React.FC<Props> = ({ children }) => {
// 使用工具栏
usePreviewTools({
setTools,
handleZoom,
handleCopyImage,
handleDownload

View File

@ -1,5 +1,5 @@
import { LoadingOutlined } from '@ant-design/icons'
import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { Spin } from 'antd'
import pako from 'pako'
import React, { memo, useCallback, useRef, useState } from 'react'
@ -134,9 +134,10 @@ const PlantUMLServerImage: React.FC<PlantUMLServerImageProps> = ({ format, diagr
interface PlantUMLProps {
children: string
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
}
const PlantUmlPreview: React.FC<PlantUMLProps> = ({ children }) => {
const PlantUmlPreview: React.FC<PlantUMLProps> = ({ children, setTools }) => {
const { t } = useTranslation()
const containerRef = useRef<HTMLDivElement>(null)
@ -165,6 +166,7 @@ const PlantUmlPreview: React.FC<PlantUMLProps> = ({ children }) => {
// 使用工具栏
usePreviewTools({
setTools,
handleZoom,
handleCopyImage,
handleDownload: customDownload

View File

@ -1,12 +1,13 @@
import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar'
import { memo, useRef } from 'react'
import styled from 'styled-components'
interface Props {
children: string
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
}
const SvgPreview: React.FC<Props> = ({ children }) => {
const SvgPreview: React.FC<Props> = ({ children, setTools }) => {
const svgContainerRef = useRef<HTMLDivElement>(null)
// 使用通用图像工具
@ -17,6 +18,7 @@ const SvgPreview: React.FC<Props> = ({ children }) => {
// 使用工具栏
usePreviewTools({
setTools,
handleCopyImage,
handleDownload
})

View File

@ -1,6 +1,6 @@
import { LoadingOutlined } from '@ant-design/icons'
import CodeEditor from '@renderer/components/CodeEditor'
import { CodeToolbar, CodeToolContext, TOOL_SPECS, useCodeToolbar } from '@renderer/components/CodeToolbar'
import { CodeTool, CodeToolbar, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar'
import { useSettings } from '@renderer/hooks/useSettings'
import { pyodideService } from '@renderer/services/PyodideService'
import { extractTitle } from '@renderer/utils/formats'
@ -49,6 +49,9 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
const [isRunning, setIsRunning] = useState(false)
const [output, setOutput] = useState('')
const [tools, setTools] = useState<CodeTool[]>([])
const { registerTool, removeTool } = useCodeTool(setTools)
const isExecutable = useMemo(() => {
return codeExecution.enabled && language === 'python'
}, [codeExecution.enabled, language])
@ -59,33 +62,17 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
return hasSpecialView && viewMode === 'special'
}, [hasSpecialView, viewMode])
const { updateContext, registerTool, removeTool } = useCodeToolbar()
const handleCopySource = useCallback(() => {
navigator.clipboard.writeText(children)
window.message.success({ content: t('code_block.copy.success'), key: 'copy-code' })
}, [children, t])
useEffect(() => {
updateContext({
code: children,
language
})
}, [children, language, updateContext])
const handleCopySource = useCallback(
(ctx?: CodeToolContext) => {
if (!ctx) return
navigator.clipboard.writeText(ctx.code)
window.message.success({ content: t('code_block.copy.success'), key: 'copy-code' })
},
[t]
)
const handleDownloadSource = useCallback((ctx?: CodeToolContext) => {
if (!ctx) return
const { code, language } = ctx
const handleDownloadSource = useCallback(() => {
let fileName = ''
// 尝试提取标题
if (language === 'html' && code.includes('</html>')) {
const title = extractTitle(code)
if (language === 'html' && children.includes('</html>')) {
const title = extractTitle(children)
if (title) {
fileName = `${title}.html`
}
@ -96,31 +83,26 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
fileName = `${dayjs().format('YYYYMMDDHHmm')}.${language}`
}
window.api.file.save(fileName, code)
}, [])
window.api.file.save(fileName, children)
}, [children, language])
const handleRunScript = useCallback(
(ctx?: CodeToolContext) => {
if (!ctx) return
const handleRunScript = useCallback(() => {
setIsRunning(true)
setOutput('')
setIsRunning(true)
setOutput('')
pyodideService
.runScript(ctx.code, {}, codeExecution.timeoutMinutes * 60000)
.then((formattedOutput) => {
setOutput(formattedOutput)
})
.catch((error) => {
console.error('Unexpected error:', error)
setOutput(`Unexpected error: ${error.message || 'Unknown error'}`)
})
.finally(() => {
setIsRunning(false)
})
},
[codeExecution.timeoutMinutes]
)
pyodideService
.runScript(children, {}, codeExecution.timeoutMinutes * 60000)
.then((formattedOutput) => {
setOutput(formattedOutput)
})
.catch((error) => {
console.error('Unexpected error:', error)
setOutput(`Unexpected error: ${error.message || 'Unknown error'}`)
})
.finally(() => {
setIsRunning(false)
})
}, [children, codeExecution.timeoutMinutes])
useEffect(() => {
// 复制按钮
@ -191,7 +173,7 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
...TOOL_SPECS.run,
icon: isRunning ? <LoadingOutlined /> : <CirclePlay className="icon" />,
tooltip: t('code_block.run'),
onClick: (ctx) => !isRunning && handleRunScript(ctx)
onClick: () => !isRunning && handleRunScript()
})
return () => isExecutable && removeTool(TOOL_SPECS.run.id)
@ -200,20 +182,32 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
// 源代码视图组件
const sourceView = useMemo(() => {
if (codeEditor.enabled) {
return <CodeEditor value={children} language={language} onSave={onSave} options={{ stream: true }} />
return (
<CodeEditor
value={children}
language={language}
onSave={onSave}
options={{ stream: true }}
setTools={setTools}
/>
)
} else {
return <CodePreview language={language}>{children}</CodePreview>
return (
<CodePreview language={language} setTools={setTools}>
{children}
</CodePreview>
)
}
}, [children, codeEditor.enabled, language, onSave])
}, [children, codeEditor.enabled, language, onSave, setTools])
// 特殊视图组件映射
const specialView = useMemo(() => {
if (language === 'mermaid') {
return <MermaidPreview>{children}</MermaidPreview>
return <MermaidPreview setTools={setTools}>{children}</MermaidPreview>
} else if (language === 'plantuml' && isValidPlantUML(children)) {
return <PlantUmlPreview>{children}</PlantUmlPreview>
return <PlantUmlPreview setTools={setTools}>{children}</PlantUmlPreview>
} else if (language === 'svg') {
return <SvgPreview>{children}</SvgPreview>
return <SvgPreview setTools={setTools}>{children}</SvgPreview>
}
return null
}, [children, language])
@ -246,7 +240,7 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
return (
<CodeBlockWrapper className="code-block" $isInSpecialView={isInSpecialView}>
{renderHeader}
<CodeToolbar />
<CodeToolbar tools={tools} />
{renderContent}
{renderArtifacts}
{isExecutable && output && <StatusBar>{output}</StatusBar>}
@ -255,10 +249,11 @@ const CodeBlockView: React.FC<Props> = ({ children, language, onSave }) => {
}
const CodeBlockWrapper = styled.div<{ $isInSpecialView: boolean }>`
/* FIXME: 在 bubble style 中撑开一些宽度*/
min-width: min(calc(60vw - var(--sidebar-width)), 700px);
position: relative;
.code-toolbar {
margin-top: ${(props) => (props.$isInSpecialView ? '20px' : '0')};
background-color: ${(props) => (props.$isInSpecialView ? 'transparent' : 'var(--color-background-mute)')};
border-radius: ${(props) => (props.$isInSpecialView ? '0' : '4px')};
opacity: 0;
@ -279,13 +274,13 @@ const CodeBlockWrapper = styled.div<{ $isInSpecialView: boolean }>`
const CodeHeader = styled.div<{ $isInSpecialView: boolean }>`
display: flex;
align-items: center;
justify-content: space-between;
color: var(--color-text);
font-size: 14px;
font-weight: bold;
padding: 0 10px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
margin-top: ${(props) => (props.$isInSpecialView ? '6px' : '0')};
height: ${(props) => (props.$isInSpecialView ? '16px' : '34px')};
`

View File

@ -1,4 +1,4 @@
import { TOOL_SPECS, useCodeToolbar } from '@renderer/components/CodeToolbar'
import { CodeTool, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar'
import { useCodeStyle } from '@renderer/context/CodeStyleProvider'
import { useSettings } from '@renderer/hooks/useSettings'
import CodeMirror, { Annotation, BasicSetupOptions, EditorView, Extension, keymap } from '@uiw/react-codemirror'
@ -25,6 +25,7 @@ interface Props {
language: string
onSave?: (newContent: string) => void
onChange?: (newContent: string) => void
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
minHeight?: string
maxHeight?: string
/** 用于覆写编辑器的某些设置 */
@ -52,6 +53,7 @@ const CodeEditor = ({
language,
onSave,
onChange,
setTools,
minHeight,
maxHeight,
options,
@ -88,7 +90,7 @@ const CodeEditor = ({
const langExtensions = useLanguageExtensions(language, options?.lint)
const { registerTool, removeTool } = useCodeToolbar()
const { registerTool, removeTool } = useCodeTool(setTools)
// 展开/折叠工具
useEffect(() => {

View File

@ -1,71 +0,0 @@
import React, { createContext, use, useCallback, useMemo, useState } from 'react'
import { CodeTool, CodeToolContext } from './types'
// 定义上下文默认值
const defaultContext: CodeToolContext = {
code: '',
language: ''
}
export interface CodeToolbarContextType {
tools: CodeTool[]
context: CodeToolContext
registerTool: (tool: CodeTool) => void
removeTool: (id: string) => void
updateContext: (newContext: Partial<CodeToolContext>) => void
}
const defaultCodeToolbarContext: CodeToolbarContextType = {
tools: [],
context: defaultContext,
registerTool: () => {},
removeTool: () => {},
updateContext: () => {}
}
const CodeToolbarContext = createContext<CodeToolbarContextType>(defaultCodeToolbarContext)
export const CodeToolbarProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
const [tools, setTools] = useState<CodeTool[]>([])
const [context, setContext] = useState<CodeToolContext>(defaultContext)
// 注册工具如果已存在同ID工具则替换
const registerTool = useCallback((tool: CodeTool) => {
setTools((prev) => {
const filtered = prev.filter((t) => t.id !== tool.id)
return [...filtered, tool].sort((a, b) => b.order - a.order)
})
}, [])
// 移除工具
const removeTool = useCallback((id: string) => {
setTools((prev) => prev.filter((tool) => tool.id !== id))
}, [])
// 更新上下文
const updateContext = useCallback((newContext: Partial<CodeToolContext>) => {
setContext((prev) => ({ ...prev, ...newContext }))
}, [])
const value: CodeToolbarContextType = useMemo(
() => ({
tools,
context,
registerTool,
removeTool,
updateContext
}),
[tools, context, registerTool, removeTool, updateContext]
)
return <CodeToolbarContext value={value}>{children}</CodeToolbarContext>
}
export const useCodeToolbar = () => {
const context = use(CodeToolbarContext)
if (!context) {
throw new Error('useCodeToolbar must be used within a CodeToolbarProvider')
}
return context
}

View File

@ -0,0 +1,26 @@
import { useCallback } from 'react'
import { CodeTool } from './types'
export const useCodeTool = (setTools?: (value: React.SetStateAction<CodeTool[]>) => void) => {
// 注册工具如果已存在同ID工具则替换
const registerTool = useCallback(
(tool: CodeTool) => {
setTools?.((prev) => {
const filtered = prev.filter((t) => t.id !== tool.id)
return [...filtered, tool].sort((a, b) => b.order - a.order)
})
},
[setTools]
)
// 移除工具
const removeTool = useCallback(
(id: string) => {
setTools?.((prev) => prev.filter((tool) => tool.id !== id))
},
[setTools]
)
return { registerTool, removeTool }
}

View File

@ -1,5 +1,5 @@
export * from './constants'
export * from './context'
export * from './hook'
export * from './toolbar'
export * from './types'
export * from './usePreviewTools'

View File

@ -5,7 +5,6 @@ import React, { memo, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import styled from 'styled-components'
import { useCodeToolbar } from './context'
import { CodeTool } from './types'
interface CodeToolButtonProps {
@ -13,22 +12,19 @@ interface CodeToolButtonProps {
}
const CodeToolButton: React.FC<CodeToolButtonProps> = memo(({ tool }) => {
const { context } = useCodeToolbar()
return (
<Tooltip key={`${tool.id}-${tool.tooltip}`} title={tool.tooltip} mouseEnterDelay={0.5}>
<ToolWrapper onClick={() => tool.onClick(context)}>{tool.icon}</ToolWrapper>
<Tooltip key={tool.id} title={tool.tooltip} mouseEnterDelay={0.5}>
<ToolWrapper onClick={() => tool.onClick()}>{tool.icon}</ToolWrapper>
</Tooltip>
)
})
export const CodeToolbar: React.FC = memo(() => {
const { tools, context } = useCodeToolbar()
export const CodeToolbar: React.FC<{ tools: CodeTool[] }> = memo(({ tools }) => {
const [showQuickTools, setShowQuickTools] = useState(false)
const { t } = useTranslation()
// 根据条件显示工具
const visibleTools = tools.filter((tool) => !tool.visible || tool.visible(context))
const visibleTools = tools.filter((tool) => !tool.visible || tool.visible())
// 按类型分组
const coreTools = visibleTools.filter((tool) => tool.type === 'core')

View File

@ -20,16 +20,6 @@ export interface CodeToolSpec {
export interface CodeTool extends CodeToolSpec {
icon: React.ReactNode
tooltip: string
visible?: (ctx?: CodeToolContext) => boolean
onClick: (ctx?: CodeToolContext) => void
}
/**
*
* @param code
* @param language
*/
export interface CodeToolContext {
code: string
language: string
visible?: () => boolean
onClick: () => void
}

View File

@ -5,7 +5,8 @@ import { useTranslation } from 'react-i18next'
import { DownloadPngIcon, DownloadSvgIcon } from '../Icons/DownloadIcons'
import { TOOL_SPECS } from './constants'
import { useCodeToolbar } from './context'
import { useCodeTool } from './hook'
import { CodeTool } from './types'
// 预编译正则表达式用于查询位置
const TRANSFORM_REGEX = /translate\((-?\d+\.?\d*)px,\s*(-?\d+\.?\d*)px\)/
@ -272,6 +273,7 @@ export const usePreviewToolHandlers = (
}
export interface PreviewToolsOptions {
setTools?: (value: React.SetStateAction<CodeTool[]>) => void
handleZoom?: (delta: number) => void
handleCopyImage?: () => Promise<void>
handleDownload?: (format: 'svg' | 'png') => void
@ -280,9 +282,9 @@ export interface PreviewToolsOptions {
/**
* Hook
*/
export const usePreviewTools = ({ handleZoom, handleCopyImage, handleDownload }: PreviewToolsOptions) => {
export const usePreviewTools = ({ setTools, handleZoom, handleCopyImage, handleDownload }: PreviewToolsOptions) => {
const { t } = useTranslation()
const { registerTool, removeTool } = useCodeToolbar()
const { registerTool, removeTool } = useCodeTool(setTools)
useEffect(() => {
// 根据提供的功能有选择性地注册工具

View File

@ -1,5 +1,4 @@
import CodeBlockView from '@renderer/components/CodeBlockView'
import { CodeToolbarProvider } from '@renderer/components/CodeToolbar'
import React, { memo, useCallback } from 'react'
interface Props {
@ -24,11 +23,9 @@ const CodeBlock: React.FC<Props> = ({ children, className, id, onSave }) => {
)
return match ? (
<CodeToolbarProvider>
<CodeBlockView language={language} onSave={handleSave}>
{children}
</CodeBlockView>
</CodeToolbarProvider>
<CodeBlockView language={language} onSave={handleSave}>
{children}
</CodeBlockView>
) : (
<code className={className} style={{ textWrap: 'wrap' }}>
{children}

View File

@ -1,6 +1,5 @@
import { nanoid } from '@reduxjs/toolkit'
import CodeEditor from '@renderer/components/CodeEditor'
import { CodeToolbarProvider } from '@renderer/components/CodeToolbar'
import { useAppDispatch } from '@renderer/store'
import { setMCPServerActive } from '@renderer/store/mcp'
import { MCPServer } from '@renderer/types'
@ -157,25 +156,23 @@ const AddMcpServerModal: FC<AddMcpServerModalProps> = ({ visible, onClose, onSuc
name="serverConfig"
label={t('settings.mcp.addServer.importFrom.tooltip')}
rules={[{ required: true, message: t('settings.mcp.addServer.importFrom.placeholder') }]}>
<CodeToolbarProvider>
<CodeEditor
// 如果表單值為空,顯示範例 JSON否則顯示表單值
value={serverConfigValue}
placeholder={initialJsonExample}
language="json"
onChange={handleEditorChange}
maxHeight="300px"
options={{
lint: true,
collapsible: true,
wrappable: true,
lineNumbers: true,
foldGutter: true,
highlightActiveLine: true,
keymap: true
}}
/>
</CodeToolbarProvider>
<CodeEditor
// 如果表單值為空,顯示範例 JSON否則顯示表單值
value={serverConfigValue}
placeholder={initialJsonExample}
language="json"
onChange={handleEditorChange}
maxHeight="300px"
options={{
lint: true,
collapsible: true,
wrappable: true,
lineNumbers: true,
foldGutter: true,
highlightActiveLine: true,
keymap: true
}}
/>
</Form.Item>
</Form>
</Modal>

View File

@ -1,5 +1,4 @@
import CodeEditor from '@renderer/components/CodeEditor'
import { CodeToolbarProvider } from '@renderer/components/CodeToolbar'
import { TopView } from '@renderer/components/TopView'
import { useAppDispatch, useAppSelector } from '@renderer/store'
import { setMCPServers } from '@renderer/store/mcp'
@ -121,23 +120,21 @@ const PopupContainer: React.FC<Props> = ({ resolve }) => {
</div>
{jsonConfig && (
<div style={{ marginBottom: '16px' }}>
<CodeToolbarProvider>
<CodeEditor
value={jsonConfig}
language="json"
onChange={(value) => setJsonConfig(value)}
maxHeight="60vh"
options={{
lint: true,
collapsible: true,
wrappable: true,
lineNumbers: true,
foldGutter: true,
highlightActiveLine: true,
keymap: true
}}
/>
</CodeToolbarProvider>
<CodeEditor
value={jsonConfig}
language="json"
onChange={(value) => setJsonConfig(value)}
maxHeight="60vh"
options={{
lint: true,
collapsible: true,
wrappable: true,
lineNumbers: true,
foldGutter: true,
highlightActiveLine: true,
keymap: true
}}
/>
</div>
)}
<Typography.Text type="secondary">{t('settings.mcp.jsonModeHint')}</Typography.Text>