diff --git a/client/src/components/Chat/Messages/Content/Markdown.tsx b/client/src/components/Chat/Messages/Content/Markdown.tsx
index 7bd6511cfa..1954b80e32 100644
--- a/client/src/components/Chat/Messages/Content/Markdown.tsx
+++ b/client/src/components/Chat/Messages/Content/Markdown.tsx
@@ -19,6 +19,7 @@ import { Citation, CompositeCitation, HighlightedText } from '~/components/Web/C
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
import { langSubset, preprocessLaTeX, handleDoubleClick } from '~/utils';
import CodeBlock from '~/components/Messages/Content/CodeBlock';
+import MarkdownErrorBoundary from './MarkdownErrorBoundary';
import useHasAccess from '~/hooks/Roles/useHasAccess';
import { unicodeCitation } from '~/components/Web';
import { useFileDownload } from '~/data-provider';
@@ -219,31 +220,33 @@ const Markdown = memo(({ content = '', isLatestMessage }: TContentProps) => {
}
return (
-
-
-
+
+
+
- {currentContent}
-
-
-
+ >
+ {currentContent}
+
+
+
+
);
});
diff --git a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
new file mode 100644
index 0000000000..15c68f7e9d
--- /dev/null
+++ b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
@@ -0,0 +1,90 @@
+import React from 'react';
+import remarkGfm from 'remark-gfm';
+import supersub from 'remark-supersub';
+import ReactMarkdown from 'react-markdown';
+import rehypeHighlight from 'rehype-highlight';
+import type { PluggableList } from 'unified';
+import { code, codeNoExecution, a, p } from './Markdown';
+import { CodeBlockProvider } from '~/Providers';
+import { langSubset } from '~/utils';
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ error?: Error;
+}
+
+interface MarkdownErrorBoundaryProps {
+ children: React.ReactNode;
+ content: string;
+ codeExecution?: boolean;
+}
+
+class MarkdownErrorBoundary extends React.Component<
+ MarkdownErrorBoundaryProps,
+ ErrorBoundaryState
+> {
+ constructor(props: MarkdownErrorBoundaryProps) {
+ super(props);
+ this.state = { hasError: false };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ return { hasError: true, error };
+ }
+
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
+ console.error('Markdown rendering error:', error, errorInfo);
+ }
+
+ componentDidUpdate(prevProps: MarkdownErrorBoundaryProps) {
+ if (prevProps.content !== this.props.content && this.state.hasError) {
+ this.setState({ hasError: false, error: undefined });
+ }
+ }
+
+ render() {
+ if (this.state.hasError) {
+ const { content, codeExecution = true } = this.props;
+
+ const rehypePlugins: PluggableList = [
+ [
+ rehypeHighlight,
+ {
+ detect: true,
+ ignoreMissing: true,
+ subset: langSubset,
+ },
+ ],
+ ];
+
+ return (
+
+
+ {content}
+
+
+ );
+ }
+
+ return this.props.children;
+ }
+}
+
+export default MarkdownErrorBoundary;
diff --git a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
index 019783607c..c3b302d0dd 100644
--- a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
+++ b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
@@ -8,6 +8,7 @@ import rehypeHighlight from 'rehype-highlight';
import type { PluggableList } from 'unified';
import { code, codeNoExecution, a, p } from './Markdown';
import { CodeBlockProvider, ArtifactProvider } from '~/Providers';
+import MarkdownErrorBoundary from './MarkdownErrorBoundary';
import { langSubset } from '~/utils';
const MarkdownLite = memo(
@@ -25,32 +26,34 @@ const MarkdownLite = memo(
];
return (
-
-
-
+
+
+
- {content}
-
-
-
+ >
+ {content}
+
+
+
+
);
},
);
diff --git a/client/src/components/Chat/Messages/Content/Part.tsx b/client/src/components/Chat/Messages/Content/Part.tsx
index c63dfe31e7..75e6d6ea17 100644
--- a/client/src/components/Chat/Messages/Content/Part.tsx
+++ b/client/src/components/Chat/Messages/Content/Part.tsx
@@ -85,7 +85,7 @@ const Part = memo(
const isToolCall =
'args' in toolCall && (!toolCall.type || toolCall.type === ToolCallTypes.TOOL_CALL);
- if (isToolCall && toolCall.name === Tools.execute_code) {
+ if (isToolCall && toolCall.name === Tools.execute_code && toolCall.args) {
return (
{
- let parsedArgs: ParsedArgs | string = args;
+ let parsedArgs: ParsedArgs | string | undefined | null = args;
try {
- parsedArgs = JSON.parse(args);
+ parsedArgs = JSON.parse(args || '');
} catch {
// console.error('Failed to parse args:', e);
}
if (typeof parsedArgs === 'object') {
return parsedArgs;
}
- const langMatch = args.match(/"lang"\s*:\s*"(\w+)"/);
- const codeMatch = args.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"(session_id|args)"|"\s*})/s);
+ const langMatch = args?.match(/"lang"\s*:\s*"(\w+)"/);
+ const codeMatch = args?.match(/"code"\s*:\s*"(.+?)(?="\s*,\s*"(session_id|args)"|"\s*})/s);
let code = '';
if (codeMatch) {
@@ -51,7 +51,7 @@ export default function ExecuteCode({
attachments,
}: {
initialProgress: number;
- args: string;
+ args?: string;
output?: string;
attachments?: TAttachment[];
}) {
@@ -65,7 +65,7 @@ export default function ExecuteCode({
const outputRef = useRef(output);
const prevShowCodeRef = useRef(showCode);
- const { lang, code } = useParseArgs(args);
+ const { lang, code } = useParseArgs(args) ?? ({} as ParsedArgs);
const progress = useProgress(initialProgress);
useEffect(() => {
@@ -144,7 +144,7 @@ export default function ExecuteCode({
onClick={() => setShowCode((prev) => !prev)}
inProgressText={localize('com_ui_analyzing')}
finishedText={localize('com_ui_analyzing_finished')}
- hasInput={!!code.length}
+ hasInput={!!code?.length}
isExpanded={showCode}
/>