From 2a72f391b71debb9b26b67319a1a602526e49bb9 Mon Sep 17 00:00:00 2001 From: one Date: Thu, 10 Jul 2025 19:32:51 +0800 Subject: [PATCH] feat: codeblock dot language (#6783) * feat(CodeBlock): support dot language in code block - render DOT using @viz-js/viz - highlight DOT using @viz-js/lang-dot (CodeEditor only) - extract a special view map, update file structure - extract and reuse the PreviewError component across special views - update dependencies, fix peer dependencies * chore: prepare for merge --- package.json | 4 + src/renderer/src/assets/styles/markdown.scss | 4 +- .../components/CodeBlockView/CodePreview.tsx | 8 +- .../CodeBlockView/GraphvizPreview.tsx | 102 +++++++++++++++ .../CodeBlockView/MermaidPreview.tsx | 22 +--- .../CodeBlockView/PlantUmlPreview.tsx | 13 +- .../components/CodeBlockView/PreviewError.tsx | 14 ++ .../components/CodeBlockView/SvgPreview.tsx | 11 +- .../src/components/CodeBlockView/constants.ts | 20 +++ .../src/components/CodeBlockView/index.ts | 2 + .../src/components/CodeBlockView/types.ts | 14 ++ .../CodeBlockView/{index.tsx => view.tsx} | 31 ++--- .../src/components/CodeEditor/hooks.ts | 122 ++++++++++++++---- .../src/context/CodeStyleProvider.tsx | 3 +- .../src/pages/home/Markdown/CodeBlock.tsx | 2 +- yarn.lock | 32 ++++- 16 files changed, 314 insertions(+), 90 deletions(-) create mode 100644 src/renderer/src/components/CodeBlockView/GraphvizPreview.tsx create mode 100644 src/renderer/src/components/CodeBlockView/PreviewError.tsx create mode 100644 src/renderer/src/components/CodeBlockView/constants.ts create mode 100644 src/renderer/src/components/CodeBlockView/index.ts create mode 100644 src/renderer/src/components/CodeBlockView/types.ts rename src/renderer/src/components/CodeBlockView/{index.tsx => view.tsx} (91%) diff --git a/package.json b/package.json index e56b1e261..c718dce1a 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "@cherrystudio/embedjs-loader-xml": "^0.1.31", "@cherrystudio/embedjs-ollama": "^0.1.31", "@cherrystudio/embedjs-openai": "^0.1.31", + "@codemirror/view": "^6.0.0", "@electron-toolkit/eslint-config-prettier": "^3.0.0", "@electron-toolkit/eslint-config-ts": "^3.0.0", "@electron-toolkit/preload": "^3.0.0", @@ -141,6 +142,8 @@ "@vitest/coverage-v8": "^3.1.4", "@vitest/ui": "^3.1.4", "@vitest/web-worker": "^3.1.4", + "@viz-js/lang-dot": "^1.0.5", + "@viz-js/viz": "^3.14.0", "@xyflow/react": "^12.4.4", "antd": "patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch", "archiver": "^7.0.1", @@ -225,6 +228,7 @@ "tiny-pinyin": "^1.3.2", "tokenx": "^1.1.0", "typescript": "^5.6.2", + "unified": "^11.0.5", "uuid": "^10.0.0", "vite": "6.2.6", "vitest": "^3.1.4", diff --git a/src/renderer/src/assets/styles/markdown.scss b/src/renderer/src/assets/styles/markdown.scss index 19e10df41..e34f64912 100644 --- a/src/renderer/src/assets/styles/markdown.scss +++ b/src/renderer/src/assets/styles/markdown.scss @@ -129,9 +129,7 @@ overflow-x: auto; font-family: 'Fira Code', 'Courier New', Courier, monospace; background-color: var(--color-background-mute); - &:has(.mermaid), - &:has(.plantuml-preview), - &:has(.svg-preview) { + &:has(.special-preview) { background-color: transparent; } &:not(pre pre) { diff --git a/src/renderer/src/components/CodeBlockView/CodePreview.tsx b/src/renderer/src/components/CodeBlockView/CodePreview.tsx index 3df9491e1..b4da2d4ee 100644 --- a/src/renderer/src/components/CodeBlockView/CodePreview.tsx +++ b/src/renderer/src/components/CodeBlockView/CodePreview.tsx @@ -1,4 +1,4 @@ -import { CodeTool, TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar' +import { TOOL_SPECS, useCodeTool } from '@renderer/components/CodeToolbar' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useCodeHighlight } from '@renderer/hooks/useCodeHighlight' import { useSettings } from '@renderer/hooks/useSettings' @@ -12,10 +12,10 @@ import { useTranslation } from 'react-i18next' import { ThemedToken } from 'shiki/core' import styled from 'styled-components' -interface CodePreviewProps { - children: string +import { BasicPreviewProps } from './types' + +interface CodePreviewProps extends BasicPreviewProps { language: string - setTools?: (value: React.SetStateAction) => void } const MAX_COLLAPSE_HEIGHT = 350 diff --git a/src/renderer/src/components/CodeBlockView/GraphvizPreview.tsx b/src/renderer/src/components/CodeBlockView/GraphvizPreview.tsx new file mode 100644 index 000000000..452ed1261 --- /dev/null +++ b/src/renderer/src/components/CodeBlockView/GraphvizPreview.tsx @@ -0,0 +1,102 @@ +import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' +import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' +import { AsyncInitializer } from '@renderer/utils/asyncInitializer' +import { Flex, Spin } from 'antd' +import { debounce } from 'lodash' +import React, { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import styled from 'styled-components' + +import PreviewError from './PreviewError' +import { BasicPreviewProps } from './types' + +// 管理 viz 实例 +const vizInitializer = new AsyncInitializer(async () => { + const module = await import('@viz-js/viz') + return await module.instance() +}) + +/** 预览 Graphviz 图表 + * 通过防抖渲染提供比较统一的体验,减少闪烁。 + */ +const GraphvizPreview: React.FC = ({ children, setTools }) => { + const graphvizRef = useRef(null) + const [error, setError] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + // 使用通用图像工具 + const { handleZoom, handleCopyImage, handleDownload } = usePreviewToolHandlers(graphvizRef, { + imgSelector: 'svg', + prefix: 'graphviz', + enableWheelZoom: true + }) + + // 使用工具栏 + usePreviewTools({ + setTools, + handleZoom, + handleCopyImage, + handleDownload + }) + + // 实际的渲染函数 + const renderGraphviz = useCallback(async (content: string) => { + if (!content || !graphvizRef.current) return + + try { + setIsLoading(true) + + const viz = await vizInitializer.get() + const svgElement = viz.renderSVGElement(content) + + // 清空容器并添加新的 SVG + graphvizRef.current.innerHTML = '' + graphvizRef.current.appendChild(svgElement) + + // 渲染成功,清除错误记录 + setError(null) + } catch (error) { + setError((error as Error).message || 'DOT syntax error or rendering failed') + } finally { + setIsLoading(false) + } + }, []) + + // debounce 渲染 + const debouncedRender = useMemo( + () => + debounce((content: string) => { + startTransition(() => renderGraphviz(content)) + }, 300), + [renderGraphviz] + ) + + // 触发渲染 + useEffect(() => { + if (children) { + setIsLoading(true) + debouncedRender(children) + } else { + debouncedRender.cancel() + setIsLoading(false) + } + + return () => { + debouncedRender.cancel() + } + }, [children, debouncedRender]) + + return ( + }> + + {error && {error}} + + + + ) +} + +const StyledGraphviz = styled.div` + overflow: auto; +` + +export default memo(GraphvizPreview) diff --git a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx index d461b2899..be3e0b7e0 100644 --- a/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx +++ b/src/renderer/src/components/CodeBlockView/MermaidPreview.tsx @@ -1,5 +1,5 @@ import { nanoid } from '@reduxjs/toolkit' -import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' +import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' import SvgSpinners180Ring from '@renderer/components/Icons/SvgSpinners180Ring' import { useMermaid } from '@renderer/hooks/useMermaid' import { Flex, Spin } from 'antd' @@ -7,16 +7,14 @@ import { debounce } from 'lodash' import React, { memo, startTransition, useCallback, useEffect, useMemo, useRef, useState } from 'react' import styled from 'styled-components' -interface Props { - children: string - setTools?: (value: React.SetStateAction) => void -} +import PreviewError from './PreviewError' +import { BasicPreviewProps } from './types' /** 预览 Mermaid 图表 * 通过防抖渲染提供比较统一的体验,减少闪烁。 * FIXME: 等将来容易判断代码块结束位置时再重构。 */ -const MermaidPreview: React.FC = ({ children, setTools }) => { +const MermaidPreview: React.FC = ({ children, setTools }) => { const { mermaid, isLoading: isLoadingMermaid, error: mermaidError } = useMermaid() const mermaidRef = useRef(null) const diagramId = useRef(`mermaid-${nanoid(6)}`).current @@ -143,7 +141,7 @@ const MermaidPreview: React.FC = ({ children, setTools }) => { return ( }> - {(mermaidError || error) && {mermaidError || error}} + {(mermaidError || error) && {mermaidError || error}} @@ -154,14 +152,4 @@ const StyledMermaid = styled.div` overflow: auto; ` -const StyledError = styled.div` - overflow: auto; - padding: 16px; - color: #ff4d4f; - border: 1px solid #ff4d4f; - border-radius: 4px; - word-wrap: break-word; - white-space: pre-wrap; -` - export default memo(MermaidPreview) diff --git a/src/renderer/src/components/CodeBlockView/PlantUmlPreview.tsx b/src/renderer/src/components/CodeBlockView/PlantUmlPreview.tsx index 35ef90e12..091605603 100644 --- a/src/renderer/src/components/CodeBlockView/PlantUmlPreview.tsx +++ b/src/renderer/src/components/CodeBlockView/PlantUmlPreview.tsx @@ -1,11 +1,13 @@ import { LoadingOutlined } from '@ant-design/icons' -import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' +import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' import { Spin } from 'antd' import pako from 'pako' import React, { memo, useCallback, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import styled from 'styled-components' +import { BasicPreviewProps } from './types' + const PlantUMLServer = 'https://www.plantuml.com/plantuml' function encode64(data: Uint8Array) { let r = '' @@ -132,12 +134,7 @@ const PlantUMLServerImage: React.FC = ({ format, diagr ) } -interface PlantUMLProps { - children: string - setTools?: (value: React.SetStateAction) => void -} - -const PlantUmlPreview: React.FC = ({ children, setTools }) => { +const PlantUmlPreview: React.FC = ({ children, setTools }) => { const { t } = useTranslation() const containerRef = useRef(null) @@ -174,7 +171,7 @@ const PlantUmlPreview: React.FC = ({ children, setTools }) => { return (
- +
) } diff --git a/src/renderer/src/components/CodeBlockView/PreviewError.tsx b/src/renderer/src/components/CodeBlockView/PreviewError.tsx new file mode 100644 index 000000000..1139dea7f --- /dev/null +++ b/src/renderer/src/components/CodeBlockView/PreviewError.tsx @@ -0,0 +1,14 @@ +import { memo } from 'react' +import { styled } from 'styled-components' + +const PreviewError = styled.div` + overflow: auto; + padding: 16px; + color: #ff4d4f; + border: 1px solid #ff4d4f; + border-radius: 4px; + word-wrap: break-word; + white-space: pre-wrap; +` + +export default memo(PreviewError) diff --git a/src/renderer/src/components/CodeBlockView/SvgPreview.tsx b/src/renderer/src/components/CodeBlockView/SvgPreview.tsx index 9180aef29..fe6010151 100644 --- a/src/renderer/src/components/CodeBlockView/SvgPreview.tsx +++ b/src/renderer/src/components/CodeBlockView/SvgPreview.tsx @@ -1,15 +1,12 @@ -import { CodeTool, usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' +import { usePreviewToolHandlers, usePreviewTools } from '@renderer/components/CodeToolbar' import { memo, useEffect, useRef } from 'react' -interface Props { - children: string - setTools?: (value: React.SetStateAction) => void -} +import { BasicPreviewProps } from './types' /** * 使用 Shadow DOM 渲染 SVG */ -const SvgPreview: React.FC = ({ children, setTools }) => { +const SvgPreview: React.FC = ({ children, setTools }) => { const svgContainerRef = useRef(null) useEffect(() => { @@ -58,7 +55,7 @@ const SvgPreview: React.FC = ({ children, setTools }) => { handleDownload }) - return
+ return
} export default memo(SvgPreview) diff --git a/src/renderer/src/components/CodeBlockView/constants.ts b/src/renderer/src/components/CodeBlockView/constants.ts new file mode 100644 index 000000000..fc6687d5f --- /dev/null +++ b/src/renderer/src/components/CodeBlockView/constants.ts @@ -0,0 +1,20 @@ +import GraphvizPreview from './GraphvizPreview' +import MermaidPreview from './MermaidPreview' +import PlantUmlPreview from './PlantUmlPreview' +import SvgPreview from './SvgPreview' + +/** + * 特殊视图语言列表 + */ +export const SPECIAL_VIEWS = ['mermaid', 'plantuml', 'svg', 'dot', 'graphviz'] + +/** + * 特殊视图组件映射表 + */ +export const SPECIAL_VIEW_COMPONENTS = { + mermaid: MermaidPreview, + plantuml: PlantUmlPreview, + svg: SvgPreview, + dot: GraphvizPreview, + graphviz: GraphvizPreview +} as const diff --git a/src/renderer/src/components/CodeBlockView/index.ts b/src/renderer/src/components/CodeBlockView/index.ts new file mode 100644 index 000000000..dfb88d252 --- /dev/null +++ b/src/renderer/src/components/CodeBlockView/index.ts @@ -0,0 +1,2 @@ +export * from './types' +export * from './view' diff --git a/src/renderer/src/components/CodeBlockView/types.ts b/src/renderer/src/components/CodeBlockView/types.ts new file mode 100644 index 000000000..5ec413658 --- /dev/null +++ b/src/renderer/src/components/CodeBlockView/types.ts @@ -0,0 +1,14 @@ +import { CodeTool } from '@renderer/components/CodeToolbar' + +/** + * 预览组件的基本 props + */ +export interface BasicPreviewProps { + children: string + setTools?: (value: React.SetStateAction) => void +} + +/** + * 视图模式 + */ +export type ViewMode = 'source' | 'special' | 'split' diff --git a/src/renderer/src/components/CodeBlockView/index.tsx b/src/renderer/src/components/CodeBlockView/view.tsx similarity index 91% rename from src/renderer/src/components/CodeBlockView/index.tsx rename to src/renderer/src/components/CodeBlockView/view.tsx index 0a03b6875..7d557d924 100644 --- a/src/renderer/src/components/CodeBlockView/index.tsx +++ b/src/renderer/src/components/CodeBlockView/view.tsx @@ -12,13 +12,10 @@ import { useTranslation } from 'react-i18next' import styled from 'styled-components' import CodePreview from './CodePreview' +import { SPECIAL_VIEW_COMPONENTS, SPECIAL_VIEWS } from './constants' import HtmlArtifactsCard from './HtmlArtifactsCard' -import MermaidPreview from './MermaidPreview' -import PlantUmlPreview from './PlantUmlPreview' import StatusBar from './StatusBar' -import SvgPreview from './SvgPreview' - -type ViewMode = 'source' | 'special' | 'split' +import { ViewMode } from './types' interface Props { children: string @@ -42,7 +39,7 @@ interface Props { * - quick 工具 * - core 工具 */ -const CodeBlockView: React.FC = ({ children, language, onSave }) => { +export const CodeBlockView: React.FC = memo(({ children, language, onSave }) => { const { t } = useTranslation() const { codeEditor, codeExecution } = useSettings() @@ -57,7 +54,7 @@ const CodeBlockView: React.FC = ({ children, language, onSave }) => { return codeExecution.enabled && language === 'python' }, [codeExecution.enabled, language]) - const hasSpecialView = useMemo(() => ['mermaid', 'plantuml', 'svg'].includes(language), [language]) + const hasSpecialView = useMemo(() => SPECIAL_VIEWS.includes(language), [language]) const isInSpecialView = useMemo(() => { return hasSpecialView && viewMode === 'special' @@ -201,14 +198,16 @@ const CodeBlockView: React.FC = ({ children, language, onSave }) => { // 特殊视图组件映射 const specialView = useMemo(() => { - if (language === 'mermaid') { - return {children} - } else if (language === 'plantuml' && isValidPlantUML(children)) { - return {children} - } else if (language === 'svg') { - return {children} + const SpecialView = SPECIAL_VIEW_COMPONENTS[language as keyof typeof SPECIAL_VIEW_COMPONENTS] + + if (!SpecialView) return null + + // PlantUML 语法验证 + if (language === 'plantuml' && !isValidPlantUML(children)) { + return null } - return null + + return {children} }, [children, language]) const renderHeader = useMemo(() => { @@ -242,7 +241,7 @@ const CodeBlockView: React.FC = ({ children, language, onSave }) => { {isExecutable && output && {output}} ) -} +}) const CodeBlockWrapper = styled.div<{ $isInSpecialView: boolean }>` position: relative; @@ -293,5 +292,3 @@ const SplitViewWrapper = styled.div` overflow: hidden; } ` - -export default memo(CodeBlockView) diff --git a/src/renderer/src/components/CodeEditor/hooks.ts b/src/renderer/src/components/CodeEditor/hooks.ts index 71d74ca3a..5a04b2178 100644 --- a/src/renderer/src/components/CodeEditor/hooks.ts +++ b/src/renderer/src/components/CodeEditor/hooks.ts @@ -12,45 +12,111 @@ const linterLoaders: Record Promise> = { } } +/** + * 特殊语言加载器 + */ +const specialLanguageLoaders: Record Promise> = { + dot: async () => { + const mod = await import('@viz-js/lang-dot') + return mod.dot() + } +} + +/** + * 加载语言扩展 + */ +async function loadLanguageExtension(language: string, languageMap: Record): Promise { + let normalizedLang = languageMap[language as keyof typeof languageMap] || language.toLowerCase() + + // 如果语言名包含 `-`,转换为驼峰命名法 + if (normalizedLang.includes('-')) { + normalizedLang = normalizedLang.replace(/-([a-z])/g, (_, char) => char.toUpperCase()) + } + + // 尝试加载特殊语言 + const specialLoader = specialLanguageLoaders[normalizedLang] + if (specialLoader) { + try { + return await specialLoader() + } catch (error) { + console.debug(`Failed to load language ${normalizedLang}`, error) + return null + } + } + + // 回退到 uiw/codemirror 包含的语言 + try { + const { loadLanguage } = await import('@uiw/codemirror-extensions-langs') + const extension = loadLanguage(normalizedLang as any) + return extension || null + } catch (error) { + console.debug(`Failed to load language ${normalizedLang}`, error) + return null + } +} + +/** + * 加载 linter 扩展 + */ +async function loadLinterExtension(language: string): Promise { + const loader = linterLoaders[language] + if (!loader) return null + + try { + return await loader() + } catch (error) { + console.debug(`Failed to load linter for ${language}`, error) + return null + } +} + +/** + * 加载语言相关扩展 + */ export const useLanguageExtensions = (language: string, lint?: boolean) => { const { languageMap } = useCodeStyle() const [extensions, setExtensions] = useState([]) - // 加载语言 useEffect(() => { - let normalizedLang = languageMap[language as keyof typeof languageMap] || language.toLowerCase() + let cancelled = false - // 如果语言名包含 `-`,转换为驼峰命名法 - if (normalizedLang.includes('-')) { - normalizedLang = normalizedLang.replace(/-([a-z])/g, (_, char) => char.toUpperCase()) - } + const loadAllExtensions = async () => { + try { + // 加载所有扩展 + const [languageResult, linterResult] = await Promise.allSettled([ + loadLanguageExtension(language, languageMap), + lint ? loadLinterExtension(language) : Promise.resolve(null) + ]) - import('@uiw/codemirror-extensions-langs') - .then(({ loadLanguage }) => { - const extension = loadLanguage(normalizedLang as any) - if (extension) { - setExtensions((prev) => [...prev, extension]) + if (cancelled) return + + const results: Extension[] = [] + + // 语言扩展 + if (languageResult.status === 'fulfilled' && languageResult.value) { + results.push(languageResult.value) } - }) - .catch((error) => { - console.debug(`Failed to load language: ${normalizedLang}`, error) - }) - }, [language, languageMap]) - useEffect(() => { - if (!lint) return + // linter 扩展 + if (linterResult.status === 'fulfilled' && linterResult.value) { + results.push(linterResult.value) + } - const loader = linterLoaders[language] - if (loader) { - loader() - .then((extension) => { - setExtensions((prev) => [...prev, extension]) - }) - .catch((error) => { - console.error(`Failed to load linter for ${language}`, error) - }) + setExtensions(results) + } catch (error) { + if (!cancelled) { + console.debug('Failed to load language extensions:', error) + setExtensions([]) + } + } } - }, [language, lint]) + + loadAllExtensions() + + return () => { + cancelled = true + } + }, [language, lint, languageMap]) return extensions } diff --git a/src/renderer/src/context/CodeStyleProvider.tsx b/src/renderer/src/context/CodeStyleProvider.tsx index e702d4847..c14364b7b 100644 --- a/src/renderer/src/context/CodeStyleProvider.tsx +++ b/src/renderer/src/context/CodeStyleProvider.tsx @@ -99,7 +99,8 @@ export const CodeStyleProvider: React.FC = ({ children }) => bash: 'shell', 'objective-c++': 'objective-cpp', svg: 'xml', - vab: 'vb' + vab: 'vb', + graphviz: 'dot' } as Record }, []) diff --git a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx index f6a290544..a496706cb 100644 --- a/src/renderer/src/pages/home/Markdown/CodeBlock.tsx +++ b/src/renderer/src/pages/home/Markdown/CodeBlock.tsx @@ -1,4 +1,4 @@ -import CodeBlockView from '@renderer/components/CodeBlockView' +import { CodeBlockView } from '@renderer/components/CodeBlockView' import React, { memo, useCallback } from 'react' interface Props { diff --git a/yarn.lock b/yarn.lock index 3514bc085..e2a45ec2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3412,7 +3412,7 @@ __metadata: languageName: node linkType: hard -"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.2.1": +"@lezer/common@npm:^1.0.0, @lezer/common@npm:^1.0.2, @lezer/common@npm:^1.0.3, @lezer/common@npm:^1.1.0, @lezer/common@npm:^1.2.0, @lezer/common@npm:^1.2.1": version: 1.2.3 resolution: "@lezer/common@npm:1.2.3" checksum: 10c0/fe9f8e111080ef94037a34ca2af1221c8d01c1763ba5ecf708a286185c76119509a5d19d924c8842172716716ddce22d7834394670c4a9432f0ba9f3b7c0f50d @@ -3515,7 +3515,7 @@ __metadata: languageName: node linkType: hard -"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.1.0, @lezer/lr@npm:^1.3.0, @lezer/lr@npm:^1.3.1, @lezer/lr@npm:^1.3.10, @lezer/lr@npm:^1.3.3, @lezer/lr@npm:^1.4.0": +"@lezer/lr@npm:^1.0.0, @lezer/lr@npm:^1.1.0, @lezer/lr@npm:^1.3.0, @lezer/lr@npm:^1.3.1, @lezer/lr@npm:^1.3.10, @lezer/lr@npm:^1.3.3, @lezer/lr@npm:^1.4.0, @lezer/lr@npm:^1.4.2": version: 1.4.2 resolution: "@lezer/lr@npm:1.4.2" dependencies: @@ -3578,7 +3578,7 @@ __metadata: languageName: node linkType: hard -"@lezer/xml@npm:^1.0.0": +"@lezer/xml@npm:^1.0.0, @lezer/xml@npm:^1.0.2": version: 1.0.6 resolution: "@lezer/xml@npm:1.0.6" dependencies: @@ -6962,6 +6962,26 @@ __metadata: languageName: node linkType: hard +"@viz-js/lang-dot@npm:^1.0.5": + version: 1.0.5 + resolution: "@viz-js/lang-dot@npm:1.0.5" + dependencies: + "@codemirror/language": "npm:^6.8.0" + "@lezer/common": "npm:^1.0.3" + "@lezer/highlight": "npm:^1.1.6" + "@lezer/lr": "npm:^1.4.2" + "@lezer/xml": "npm:^1.0.2" + checksum: 10c0/86e81bf077e0a6f418fe2d5cfd8d7f7a7c032bdec13e5dfe3d21620c548e674832f6c9b300eeaad7b0842a3c4044d4ce33d5af9e359ae1efeda0a84d772b77a4 + languageName: node + linkType: hard + +"@viz-js/viz@npm:^3.14.0": + version: 3.14.0 + resolution: "@viz-js/viz@npm:3.14.0" + checksum: 10c0/901afa2d99e8f33cc4abf352f1559e0c16958e01f0750a65a33799aebfe175a18d74f6945f1ff93f64b53b69976dc3d07d39d65c58dda955abd0979dacc4294c + languageName: node + linkType: hard + "@vue/compiler-core@npm:3.5.17": version: 3.5.17 resolution: "@vue/compiler-core@npm:3.5.17" @@ -7075,6 +7095,7 @@ __metadata: "@cherrystudio/embedjs-openai": "npm:^0.1.31" "@cherrystudio/mac-system-ocr": "npm:^0.2.2" "@cherrystudio/pdf-to-img-napi": "npm:^0.0.1" + "@codemirror/view": "npm:^6.0.0" "@electron-toolkit/eslint-config-prettier": "npm:^3.0.0" "@electron-toolkit/eslint-config-ts": "npm:^3.0.0" "@electron-toolkit/preload": "npm:^3.0.0" @@ -7127,6 +7148,8 @@ __metadata: "@vitest/coverage-v8": "npm:^3.1.4" "@vitest/ui": "npm:^3.1.4" "@vitest/web-worker": "npm:^3.1.4" + "@viz-js/lang-dot": "npm:^1.0.5" + "@viz-js/viz": "npm:^3.14.0" "@xyflow/react": "npm:^12.4.4" antd: "patch:antd@npm%3A5.24.7#~/.yarn/patches/antd-npm-5.24.7-356a553ae5.patch" archiver: "npm:^7.0.1" @@ -7221,6 +7244,7 @@ __metadata: tokenx: "npm:^1.1.0" turndown: "npm:7.2.0" typescript: "npm:^5.6.2" + unified: "npm:^11.0.5" uuid: "npm:^10.0.0" vite: "npm:6.2.6" vitest: "npm:^3.1.4" @@ -19565,7 +19589,7 @@ __metadata: languageName: node linkType: hard -"unified@npm:^11.0.0": +"unified@npm:^11.0.0, unified@npm:^11.0.5": version: 11.0.5 resolution: "unified@npm:11.0.5" dependencies: