📊 feat: Render Inline Mermaid Diagrams (#11112)

* chore: add mermaid, swr, ts-md5 packages

* WIP: first pass, inline mermaid

* feat: Enhance Mermaid component with zoom, pan, and error handling features

* feat: Update Mermaid component styles for improved UI consistency

* feat: Improve Mermaid rendering with enhanced debouncing and error handling

* refactor: Update Mermaid component styles and enhance error handling in useMermaid hook

* feat: Enhance security settings in useMermaid configuration to prevent DoS attacks

* feat: Add dialog for expanded Mermaid view with zoom and pan controls

* feat: Implement auto-scroll for streaming code in Mermaid component

* feat: Replace loading spinner with reusable Spinner component in Mermaid

* feat: Sanitize SVG output in useMermaid to enhance security

* feat: Enhance SVG sanitization in useMermaid to support additional elements for text rendering

* refactor: Enhance initial content check in useDebouncedMermaid for improved rendering logic

* feat: Refactor Mermaid component to use Button component and enhance focus management for code toggling and copying

* chore: remove unused key

* refactor: initial content check in useDebouncedMermaid to detect significant content changes
This commit is contained in:
Danny Avila 2025-12-26 19:53:06 -05:00 committed by GitHub
parent 43c2c20dd7
commit 3503b7caeb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 2321 additions and 9 deletions

View file

@ -3,6 +3,7 @@ import { useRecoilValue } from 'recoil';
import { useToastContext } from '@librechat/client';
import { PermissionTypes, Permissions, apiBaseUrl } from 'librechat-data-provider';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
import Mermaid from '~/components/Messages/Content/Mermaid';
import useHasAccess from '~/hooks/Roles/useHasAccess';
import { useFileDownload } from '~/data-provider';
import { useCodeBlockContext } from '~/Providers';
@ -24,10 +25,11 @@ export const code: React.ElementType = memo(({ className, children }: TCodeProps
const match = /language-(\w+)/.exec(className ?? '');
const lang = match && match[1];
const isMath = lang === 'math';
const isMermaid = lang === 'mermaid';
const isSingleLine = typeof children === 'string' && children.split('\n').length === 1;
const { getNextIndex, resetCounter } = useCodeBlockContext();
const blockIndex = useRef(getNextIndex(isMath || isSingleLine)).current;
const blockIndex = useRef(getNextIndex(isMath || isMermaid || isSingleLine)).current;
useEffect(() => {
resetCounter();
@ -35,6 +37,9 @@ export const code: React.ElementType = memo(({ className, children }: TCodeProps
if (isMath) {
return <>{children}</>;
} else if (isMermaid) {
const content = typeof children === 'string' ? children : String(children);
return <Mermaid id={`mermaid-${blockIndex}`}>{content}</Mermaid>;
} else if (isSingleLine) {
return (
<code onDoubleClick={handleDoubleClick} className={className}>
@ -59,6 +64,9 @@ export const codeNoExecution: React.ElementType = memo(({ className, children }:
if (lang === 'math') {
return children;
} else if (lang === 'mermaid') {
const content = typeof children === 'string' ? children : String(children);
return <Mermaid>{content}</Mermaid>;
} else if (typeof children === 'string' && children.split('\n').length === 1) {
return (
<code onDoubleClick={handleDoubleClick} className={className}>