LibreChat/client/src/components/Chat/Messages/Content/Markdown.tsx
catmeme 7aa8d49f3a
🧭 fix: Add Base Path Support for Login/Register and Image Paths (#10116)
* fix: add basePath pattern to support login/register and image paths

* Fix linter errors

* refactor: Update import statements for getBasePath and isEnabled, and add path utility functions with tests

- Refactored imports in addImages.js and StableDiffusion.js to use getBasePath from '@librechat/api'.
- Consolidated isEnabled and getBasePath imports in validateImageRequest.js.
- Introduced new path utility functions in path.ts and corresponding unit tests in path.spec.ts to validate base path extraction logic.

* fix: Update domain server base URL in MarkdownComponents and refactor authentication redirection logic

- Changed the domain server base URL in MarkdownComponents.tsx to use the API base URL.
- Refactored the useAuthRedirect hook to utilize React Router's navigate for redirection instead of window.location, ensuring a smoother SPA experience.
- Added unit tests for the useAuthRedirect hook to verify authentication redirection behavior.

* test: Mock isEnabled in validateImages.spec.js for improved test isolation

- Updated validateImages.spec.js to mock the isEnabled function from @librechat/api, ensuring that tests can run independently of the actual implementation.
- Cleared the DOMAIN_CLIENT environment variable before tests to avoid interference with basePath resolution.

---------

Co-authored-by: Danny Avila <danny@librechat.ai>
2025-11-21 11:25:14 -05:00

102 lines
2.8 KiB
TypeScript

import React, { memo, useMemo } from 'react';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import supersub from 'remark-supersub';
import rehypeKatex from 'rehype-katex';
import { useRecoilValue } from 'recoil';
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import remarkDirective from 'remark-directive';
import type { Pluggable } from 'unified';
import { Citation, CompositeCitation, HighlightedText } from '~/components/Web/Citation';
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
import { ArtifactProvider, CodeBlockProvider } from '~/Providers';
import MarkdownErrorBoundary from './MarkdownErrorBoundary';
import { langSubset, preprocessLaTeX } from '~/utils';
import { unicodeCitation } from '~/components/Web';
import { code, a, p, img } from './MarkdownComponents';
import store from '~/store';
type TContentProps = {
content: string;
isLatestMessage: boolean;
};
const Markdown = memo(({ content = '', isLatestMessage }: TContentProps) => {
const LaTeXParsing = useRecoilValue<boolean>(store.LaTeXParsing);
const isInitializing = content === '';
const currentContent = useMemo(() => {
if (isInitializing) {
return '';
}
return LaTeXParsing ? preprocessLaTeX(content) : content;
}, [content, LaTeXParsing, isInitializing]);
const rehypePlugins = useMemo(
() => [
[rehypeKatex],
[
rehypeHighlight,
{
detect: true,
ignoreMissing: true,
subset: langSubset,
},
],
],
[],
);
const remarkPlugins: Pluggable[] = [
supersub,
remarkGfm,
remarkDirective,
artifactPlugin,
[remarkMath, { singleDollarTextMath: false }],
unicodeCitation,
];
if (isInitializing) {
return (
<div className="absolute">
<p className="relative">
<span className={isLatestMessage ? 'result-thinking' : ''} />
</p>
</div>
);
}
return (
<MarkdownErrorBoundary content={content} codeExecution={true}>
<ArtifactProvider>
<CodeBlockProvider>
<ReactMarkdown
/** @ts-ignore */
remarkPlugins={remarkPlugins}
/* @ts-ignore */
rehypePlugins={rehypePlugins}
components={
{
code,
a,
p,
img,
artifact: Artifact,
citation: Citation,
'highlighted-text': HighlightedText,
'composite-citation': CompositeCitation,
} as {
[nodeType: string]: React.ElementType;
}
}
>
{currentContent}
</ReactMarkdown>
</CodeBlockProvider>
</ArtifactProvider>
</MarkdownErrorBoundary>
);
});
export default Markdown;