From 79fdae79472b6e7620222fcdc1742831ede2c9e2 Mon Sep 17 00:00:00 2001 From: Gavan <994259213@qq.com> Date: Thu, 3 Jul 2025 19:11:41 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E5=AF=B9=E8=AF=9D?= =?UTF-8?q?=E8=AE=B0=E5=BD=95=20diff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/src/components/markDown/diff.tsx | 40 +++++-- ui/src/components/markDown/index.tsx | 109 +++++++++++++----- ui/src/pages/auth/index.tsx | 13 +-- .../completion/completionDetailModal.tsx | 7 -- ui/src/pages/completion/index.tsx | 2 +- ui/src/theme.ts | 1 + 6 files changed, 118 insertions(+), 54 deletions(-) diff --git a/ui/src/components/markDown/diff.tsx b/ui/src/components/markDown/diff.tsx index e3303cd..f2e7fc1 100644 --- a/ui/src/components/markDown/diff.tsx +++ b/ui/src/components/markDown/diff.tsx @@ -1,4 +1,4 @@ -import React, { useRef } from 'react'; +import React, { useRef, useEffect } from 'react'; import { DiffEditor } from '@monaco-editor/react'; interface DiffProps { @@ -14,6 +14,30 @@ const Diff: React.FC = ({ language = 'javascript', height = 400, }) => { + const editorRef = useRef(null); + const monacoRef = useRef(null); + + // 卸载时主动 dispose + useEffect(() => { + return () => { + if (editorRef.current && monacoRef.current) { + const editor = editorRef.current; + // DiffEditor getModel() 返回 [original, modified] + const models = editor.getModel ? editor.getModel() : []; + if (models && Array.isArray(models)) { + models.forEach( + (model: any) => model && model.dispose && model.dispose() + ); + } + } + }; + }, []); + + const handleMount = (editor: any, monaco: any) => { + editorRef.current = editor; + monacoRef.current = monaco; + }; + // 处理高度和宽度样式 const boxHeight = typeof height === 'number' ? `${height}px` : height; const boxWidth = 1000; // 默认宽度800px @@ -27,25 +51,19 @@ const Diff: React.FC = ({ height='100%' width='100%' language={language} - original={original} - modified={modified} + original={original || ''} + modified={modified || ''} theme='vs-dark' + onMount={handleMount} options={{ readOnly: true, minimap: { enabled: false }, fontSize: 14, scrollBeyondLastLine: false, - wordWrap: 'on', + wordWrap: 'off', lineNumbers: 'on', glyphMargin: false, folding: false, - scrollbar: { - vertical: 'hidden', - horizontal: 'hidden', - handleMouseWheel: false, - alwaysConsumeMouseWheel: false, - useShadows: false, - }, overviewRulerLanes: 0, guides: { indentation: true, diff --git a/ui/src/components/markDown/index.tsx b/ui/src/components/markDown/index.tsx index 25c313f..3b683a1 100644 --- a/ui/src/components/markDown/index.tsx +++ b/ui/src/components/markDown/index.tsx @@ -43,13 +43,80 @@ export const toolNames = [ // 去掉下划线的标签名,用于Markdown渲染 export const toolTagNames = toolNames.map((name) => name.replace(/_/g, '')); -type ToolInfo = any; +// 支持多组 diff 分隔符,容错处理 +function parseAndMergeDiffs(diffText: string) { + const diffBlocks: { search: string; replace: string }[] = []; + const lines = diffText.split('\n'); + let inDiff = false; + let inSearch = false; + let inReplace = false; + let searchBuffer: string[] = []; + let replaceBuffer: string[] = []; + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + if (/^<+ *SEARCH/.test(line)) { + inDiff = true; + inSearch = true; + inReplace = false; + searchBuffer = []; + replaceBuffer = []; + continue; + } + if (/^====+$/.test(line)) { + if (inDiff && inSearch) { + inSearch = false; + inReplace = true; + continue; + } + } + if (/^>+ *REPLACE/.test(line)) { + if (inDiff && inReplace) { + diffBlocks.push({ + search: searchBuffer.join('\n'), + replace: replaceBuffer.join('\n'), + }); + inDiff = false; + inReplace = false; + continue; + } + } + if (inDiff) { + if (inSearch) { + searchBuffer.push(line); + } else if (inReplace) { + replaceBuffer.push(line); + } + } + } + // 容错:如果最后一组没有正常结束 + if (inDiff) { + diffBlocks.push({ + search: searchBuffer.join('\n'), + replace: replaceBuffer.join('\n'), + }); + } + + const mergedSearch = diffBlocks.map((b) => b.search).join('\n'); + const mergedReplace = diffBlocks.map((b) => b.replace).join('\n'); + + return { mergedSearch, mergedReplace, diffBlocks }; +} // 预处理 markdown,提取所有 内容,生成 diffMap function preprocessMarkdown(mdContent: string) { let diffIndex = 0; const diffMap: Record = {}; - const newMd = mdContent.replace( + // 自动补全未闭合的 + let fixedMd = mdContent; + const openDiffCount = (fixedMd.match(//g) || []).length; + const closeDiffCount = (fixedMd.match(/<\/diff>/g) || []).length; + if (openDiffCount > closeDiffCount) { + // 补全缺失的 + for (let i = 0; i < openDiffCount - closeDiffCount; i++) { + fixedMd += ''; + } + } + const newMd = fixedMd.replace( /([\s\S]*?)<\/diff>/g, (_, diffContent) => { const id = `diff-${diffIndex++}`; @@ -63,22 +130,11 @@ function preprocessMarkdown(mdContent: string) { const MarkDown = ({ loading = false, content, - showToolInfo = {}, - setShowToolInfo, - setCurrentToolId, - handleSearchAbort, }: { loading?: boolean; content: string; - showToolInfo: Record; - setShowToolInfo: (value: Record) => void; - setCurrentToolId?: (value: string) => void; - handleSearchAbort?: () => void; }) => { const theme = useTheme(); - const [diffContent, setDiffContent] = useState([]); - const [showThink, setShowThink] = useState(false); - const editorRef = useRef(null); // 删除 content 中 标签,并保留标签中的内容 const deleteTags = (content: string) => { @@ -176,25 +232,22 @@ const MarkDown = ({ // 去掉 user-content- 前缀 const id = node?.properties?.id?.replace(/^user-content-/, ''); const rawDiff = id ? diffMap[id] : ''; - // 解析 rawDiff 为 original 和 modified let original = '', modified = ''; if (rawDiff) { - const match = rawDiff.match( - /<{2,} *SEARCH([\s\S]*?)={2,}([\s\S]*?)>{2,} *REPLACE/ - ); - if (match) { - // 清理行号标记和分隔线 - const cleanDiff = (str: string) => - str - .replace(/:start_line:\d+\n?[-=]+/g, '') - .replace(/^-{2,}\n?/gm, '') - .replace(/^={2,}\n?/gm, '') - .replace(/^\s+|\s+$/g, ''); - original = cleanDiff(match[1].trim()); - modified = cleanDiff(match[2].trim()); - } + const { mergedSearch, mergedReplace } = + parseAndMergeDiffs(rawDiff); + // 清理行号标记和分隔线 + const cleanDiff = (str: string) => + str + .replace(/:start_line:\d+\n?[-=]+/g, '') + .replace(/^-{2,}\n?/gm, '') + .replace(/^={2,}\n?/gm, '') + .replace(/^\n+|\n+$/g, ''); + original = cleanDiff(mergedSearch); + modified = cleanDiff(mergedReplace); } + return ( { // 渲染登录表单 const renderLoginForm = () => ( <> - - - - Monkey Code - - - {renderUsernameField()} @@ -322,6 +315,12 @@ const AuthPage = () => { return ( + + + + Monkey Code + + {!loginSetting.disable_password_login && renderLoginForm()} {loginSetting.enable_dingtalk_oauth && dingdingLogin()} diff --git a/ui/src/pages/completion/completionDetailModal.tsx b/ui/src/pages/completion/completionDetailModal.tsx index c07110b..c14b684 100644 --- a/ui/src/pages/completion/completionDetailModal.tsx +++ b/ui/src/pages/completion/completionDetailModal.tsx @@ -125,13 +125,6 @@ const ChatDetailModal = ({ lineNumbers: 'on', glyphMargin: false, folding: false, - scrollbar: { - vertical: 'hidden', - horizontal: 'hidden', - handleMouseWheel: false, - alwaysConsumeMouseWheel: false, - useShadows: false, - }, overviewRulerLanes: 0, guides: { indentation: true, diff --git a/ui/src/pages/completion/index.tsx b/ui/src/pages/completion/index.tsx index 334e164..f6e40c0 100644 --- a/ui/src/pages/completion/index.tsx +++ b/ui/src/pages/completion/index.tsx @@ -65,7 +65,7 @@ const Completion = () => { const [filterLang, setFilterLang] = useState(''); const [filterAccept, setFilterAccept] = useState< 'accepted' | 'unaccepted' | '' - >(''); + >('accepted'); const { data: userOptions = { users: [] } } = useRequest(() => getListUser({ diff --git a/ui/src/theme.ts b/ui/src/theme.ts index 1c791cd..9e6a741 100644 --- a/ui/src/theme.ts +++ b/ui/src/theme.ts @@ -85,6 +85,7 @@ const lightTheme = createTheme( borderWidth: '1px !important', }, borderRadius: '10px !important', + fontSize: 14, }, }, },