diff --git a/client/src/components/Chat/Messages/Content/Markdown.tsx b/client/src/components/Chat/Messages/Content/Markdown.tsx
index 1954b80e3..7ade77564 100644
--- a/client/src/components/Chat/Messages/Content/Markdown.tsx
+++ b/client/src/components/Chat/Messages/Content/Markdown.tsx
@@ -1,4 +1,4 @@
-import React, { memo, useMemo, useRef, useEffect } from 'react';
+import React, { memo, useMemo } from 'react';
import remarkGfm from 'remark-gfm';
import remarkMath from 'remark-math';
import supersub from 'remark-supersub';
@@ -7,168 +7,16 @@ import { useRecoilValue } from 'recoil';
import ReactMarkdown from 'react-markdown';
import rehypeHighlight from 'rehype-highlight';
import remarkDirective from 'remark-directive';
-import { PermissionTypes, Permissions } from 'librechat-data-provider';
import type { Pluggable } from 'unified';
-import {
- useToastContext,
- ArtifactProvider,
- CodeBlockProvider,
- useCodeBlockContext,
-} from '~/Providers';
import { Citation, CompositeCitation, HighlightedText } from '~/components/Web/Citation';
import { Artifact, artifactPlugin } from '~/components/Artifacts/Artifact';
-import { langSubset, preprocessLaTeX, handleDoubleClick } from '~/utils';
-import CodeBlock from '~/components/Messages/Content/CodeBlock';
+import { ArtifactProvider, CodeBlockProvider } from '~/Providers';
import MarkdownErrorBoundary from './MarkdownErrorBoundary';
-import useHasAccess from '~/hooks/Roles/useHasAccess';
+import { langSubset, preprocessLaTeX } from '~/utils';
import { unicodeCitation } from '~/components/Web';
-import { useFileDownload } from '~/data-provider';
-import useLocalize from '~/hooks/useLocalize';
+import { code, a, p } from './MarkdownComponents';
import store from '~/store';
-type TCodeProps = {
- inline?: boolean;
- className?: string;
- children: React.ReactNode;
-};
-
-export const code: React.ElementType = memo(({ className, children }: TCodeProps) => {
- const canRunCode = useHasAccess({
- permissionType: PermissionTypes.RUN_CODE,
- permission: Permissions.USE,
- });
- const match = /language-(\w+)/.exec(className ?? '');
- const lang = match && match[1];
- const isMath = lang === 'math';
- const isSingleLine = typeof children === 'string' && children.split('\n').length === 1;
-
- const { getNextIndex, resetCounter } = useCodeBlockContext();
- const blockIndex = useRef(getNextIndex(isMath || isSingleLine)).current;
-
- useEffect(() => {
- resetCounter();
- }, [children, resetCounter]);
-
- if (isMath) {
- return <>{children}>;
- } else if (isSingleLine) {
- return (
-
- {children}
-
- );
- } else {
- return (
-
- );
- }
-});
-
-export const codeNoExecution: React.ElementType = memo(({ className, children }: TCodeProps) => {
- const match = /language-(\w+)/.exec(className ?? '');
- const lang = match && match[1];
-
- if (lang === 'math') {
- return children;
- } else if (typeof children === 'string' && children.split('\n').length === 1) {
- return (
-
- {children}
-
- );
- } else {
- return ;
- }
-});
-
-type TAnchorProps = {
- href: string;
- children: React.ReactNode;
-};
-
-export const a: React.ElementType = memo(({ href, children }: TAnchorProps) => {
- const user = useRecoilValue(store.user);
- const { showToast } = useToastContext();
- const localize = useLocalize();
-
- const {
- file_id = '',
- filename = '',
- filepath,
- } = useMemo(() => {
- const pattern = new RegExp(`(?:files|outputs)/${user?.id}/([^\\s]+)`);
- const match = href.match(pattern);
- if (match && match[0]) {
- const path = match[0];
- const parts = path.split('/');
- const name = parts.pop();
- const file_id = parts.pop();
- return { file_id, filename: name, filepath: path };
- }
- return { file_id: '', filename: '', filepath: '' };
- }, [user?.id, href]);
-
- const { refetch: downloadFile } = useFileDownload(user?.id ?? '', file_id);
- const props: { target?: string; onClick?: React.MouseEventHandler } = { target: '_new' };
-
- if (!file_id || !filename) {
- return (
-
- {children}
-
- );
- }
-
- const handleDownload = async (event: React.MouseEvent) => {
- event.preventDefault();
- try {
- const stream = await downloadFile();
- if (stream.data == null || stream.data === '') {
- console.error('Error downloading file: No data found');
- showToast({
- status: 'error',
- message: localize('com_ui_download_error'),
- });
- return;
- }
- const link = document.createElement('a');
- link.href = stream.data;
- link.setAttribute('download', filename);
- document.body.appendChild(link);
- link.click();
- document.body.removeChild(link);
- window.URL.revokeObjectURL(stream.data);
- } catch (error) {
- console.error('Error downloading file:', error);
- }
- };
-
- props.onClick = handleDownload;
- props.target = '_blank';
-
- return (
-
- {children}
-
- );
-});
-
-type TParagraphProps = {
- children: React.ReactNode;
-};
-
-export const p: React.ElementType = memo(({ children }: TParagraphProps) => {
- return {children}
;
-});
-
type TContentProps = {
content: string;
isLatestMessage: boolean;
diff --git a/client/src/components/Chat/Messages/Content/MarkdownComponents.tsx b/client/src/components/Chat/Messages/Content/MarkdownComponents.tsx
new file mode 100644
index 000000000..e0a381ff5
--- /dev/null
+++ b/client/src/components/Chat/Messages/Content/MarkdownComponents.tsx
@@ -0,0 +1,153 @@
+import React, { memo, useMemo, useRef, useEffect } from 'react';
+import { useRecoilValue } from 'recoil';
+import { PermissionTypes, Permissions } from 'librechat-data-provider';
+import { useToastContext, useCodeBlockContext } from '~/Providers';
+import CodeBlock from '~/components/Messages/Content/CodeBlock';
+import useHasAccess from '~/hooks/Roles/useHasAccess';
+import { useFileDownload } from '~/data-provider';
+import useLocalize from '~/hooks/useLocalize';
+import { handleDoubleClick } from '~/utils';
+import store from '~/store';
+
+type TCodeProps = {
+ inline?: boolean;
+ className?: string;
+ children: React.ReactNode;
+};
+
+export const code: React.ElementType = memo(({ className, children }: TCodeProps) => {
+ const canRunCode = useHasAccess({
+ permissionType: PermissionTypes.RUN_CODE,
+ permission: Permissions.USE,
+ });
+ const match = /language-(\w+)/.exec(className ?? '');
+ const lang = match && match[1];
+ const isMath = lang === 'math';
+ const isSingleLine = typeof children === 'string' && children.split('\n').length === 1;
+
+ const { getNextIndex, resetCounter } = useCodeBlockContext();
+ const blockIndex = useRef(getNextIndex(isMath || isSingleLine)).current;
+
+ useEffect(() => {
+ resetCounter();
+ }, [children, resetCounter]);
+
+ if (isMath) {
+ return <>{children}>;
+ } else if (isSingleLine) {
+ return (
+
+ {children}
+
+ );
+ } else {
+ return (
+
+ );
+ }
+});
+
+export const codeNoExecution: React.ElementType = memo(({ className, children }: TCodeProps) => {
+ const match = /language-(\w+)/.exec(className ?? '');
+ const lang = match && match[1];
+
+ if (lang === 'math') {
+ return children;
+ } else if (typeof children === 'string' && children.split('\n').length === 1) {
+ return (
+
+ {children}
+
+ );
+ } else {
+ return ;
+ }
+});
+
+type TAnchorProps = {
+ href: string;
+ children: React.ReactNode;
+};
+
+export const a: React.ElementType = memo(({ href, children }: TAnchorProps) => {
+ const user = useRecoilValue(store.user);
+ const { showToast } = useToastContext();
+ const localize = useLocalize();
+
+ const {
+ file_id = '',
+ filename = '',
+ filepath,
+ } = useMemo(() => {
+ const pattern = new RegExp(`(?:files|outputs)/${user?.id}/([^\\s]+)`);
+ const match = href.match(pattern);
+ if (match && match[0]) {
+ const path = match[0];
+ const parts = path.split('/');
+ const name = parts.pop();
+ const file_id = parts.pop();
+ return { file_id, filename: name, filepath: path };
+ }
+ return { file_id: '', filename: '', filepath: '' };
+ }, [user?.id, href]);
+
+ const { refetch: downloadFile } = useFileDownload(user?.id ?? '', file_id);
+ const props: { target?: string; onClick?: React.MouseEventHandler } = { target: '_new' };
+
+ if (!file_id || !filename) {
+ return (
+
+ {children}
+
+ );
+ }
+
+ const handleDownload = async (event: React.MouseEvent) => {
+ event.preventDefault();
+ try {
+ const stream = await downloadFile();
+ if (stream.data == null || stream.data === '') {
+ console.error('Error downloading file: No data found');
+ showToast({
+ status: 'error',
+ message: localize('com_ui_download_error'),
+ });
+ return;
+ }
+ const link = document.createElement('a');
+ link.href = stream.data;
+ link.setAttribute('download', filename);
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+ window.URL.revokeObjectURL(stream.data);
+ } catch (error) {
+ console.error('Error downloading file:', error);
+ }
+ };
+
+ props.onClick = handleDownload;
+ props.target = '_blank';
+
+ return (
+
+ {children}
+
+ );
+});
+
+type TParagraphProps = {
+ children: React.ReactNode;
+};
+
+export const p: React.ElementType = memo(({ children }: TParagraphProps) => {
+ return {children}
;
+});
diff --git a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
index 15c68f7e9..0342c60f8 100644
--- a/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
+++ b/client/src/components/Chat/Messages/Content/MarkdownErrorBoundary.tsx
@@ -4,7 +4,7 @@ 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 { code, codeNoExecution, a, p } from './MarkdownComponents';
import { CodeBlockProvider } from '~/Providers';
import { langSubset } from '~/utils';
diff --git a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
index c3b302d0d..d553e6b70 100644
--- a/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
+++ b/client/src/components/Chat/Messages/Content/MarkdownLite.tsx
@@ -6,7 +6,7 @@ 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 { code, codeNoExecution, a, p } from './MarkdownComponents';
import { CodeBlockProvider, ArtifactProvider } from '~/Providers';
import MarkdownErrorBoundary from './MarkdownErrorBoundary';
import { langSubset } from '~/utils';
diff --git a/client/src/components/Prompts/Groups/VariableForm.tsx b/client/src/components/Prompts/Groups/VariableForm.tsx
index 09bdcd40d..48ad704f8 100644
--- a/client/src/components/Prompts/Groups/VariableForm.tsx
+++ b/client/src/components/Prompts/Groups/VariableForm.tsx
@@ -8,8 +8,8 @@ import rehypeHighlight from 'rehype-highlight';
import { replaceSpecialVars } from 'librechat-data-provider';
import { useForm, useFieldArray, Controller, useWatch } from 'react-hook-form';
import type { TPromptGroup } from 'librechat-data-provider';
+import { codeNoExecution } from '~/components/Chat/Messages/Content/MarkdownComponents';
import { cn, wrapVariable, defaultTextProps, extractVariableInfo } from '~/utils';
-import { codeNoExecution } from '~/components/Chat/Messages/Content/Markdown';
import { TextareaAutosize, InputCombobox, Button } from '~/components/ui';
import { useAuthContext, useLocalize, useSubmitMessage } from '~/hooks';
import { PromptVariableGfm } from '../Markdown';
diff --git a/client/src/components/Prompts/PromptDetails.tsx b/client/src/components/Prompts/PromptDetails.tsx
index 0bfd8e993..62e9d02e3 100644
--- a/client/src/components/Prompts/PromptDetails.tsx
+++ b/client/src/components/Prompts/PromptDetails.tsx
@@ -7,7 +7,7 @@ import supersub from 'remark-supersub';
import rehypeHighlight from 'rehype-highlight';
import { replaceSpecialVars } from 'librechat-data-provider';
import type { TPromptGroup } from 'librechat-data-provider';
-import { codeNoExecution } from '~/components/Chat/Messages/Content/Markdown';
+import { codeNoExecution } from '~/components/Chat/Messages/Content/MarkdownComponents';
import { useLocalize, useAuthContext } from '~/hooks';
import CategoryIcon from './Groups/CategoryIcon';
import PromptVariables from './PromptVariables';
diff --git a/client/src/components/Prompts/PromptEditor.tsx b/client/src/components/Prompts/PromptEditor.tsx
index f10a94c11..75584f849 100644
--- a/client/src/components/Prompts/PromptEditor.tsx
+++ b/client/src/components/Prompts/PromptEditor.tsx
@@ -9,7 +9,7 @@ import rehypeKatex from 'rehype-katex';
import remarkMath from 'remark-math';
import supersub from 'remark-supersub';
import ReactMarkdown from 'react-markdown';
-import { codeNoExecution } from '~/components/Chat/Messages/Content/Markdown';
+import { codeNoExecution } from '~/components/Chat/Messages/Content/MarkdownComponents';
import AlwaysMakeProd from '~/components/Prompts/Groups/AlwaysMakeProd';
import { SaveIcon, CrossIcon } from '~/components/svg';
import VariablesDropdown from './VariablesDropdown';
diff --git a/client/src/components/SidePanel/Agents/Version/VersionContent.tsx b/client/src/components/SidePanel/Agents/Version/VersionContent.tsx
index 25b8f5bd5..0aa1fc4af 100644
--- a/client/src/components/SidePanel/Agents/Version/VersionContent.tsx
+++ b/client/src/components/SidePanel/Agents/Version/VersionContent.tsx
@@ -1,7 +1,7 @@
import { Spinner } from '~/components/svg';
import { useLocalize } from '~/hooks';
import VersionItem from './VersionItem';
-import { VersionContext } from './VersionPanel';
+import type { VersionContext } from './types';
type VersionContentProps = {
selectedAgentId: string;
diff --git a/client/src/components/SidePanel/Agents/Version/VersionItem.tsx b/client/src/components/SidePanel/Agents/Version/VersionItem.tsx
index c1d27cada..931eb09a8 100644
--- a/client/src/components/SidePanel/Agents/Version/VersionItem.tsx
+++ b/client/src/components/SidePanel/Agents/Version/VersionItem.tsx
@@ -1,5 +1,5 @@
import { useLocalize } from '~/hooks';
-import { VersionRecord } from './VersionPanel';
+import type { VersionRecord } from './types';
type VersionItemProps = {
version: VersionRecord;
diff --git a/client/src/components/SidePanel/Agents/Version/VersionPanel.tsx b/client/src/components/SidePanel/Agents/Version/VersionPanel.tsx
index 0f8919921..57048f66f 100644
--- a/client/src/components/SidePanel/Agents/Version/VersionPanel.tsx
+++ b/client/src/components/SidePanel/Agents/Version/VersionPanel.tsx
@@ -1,44 +1,13 @@
import { ChevronLeft } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import { useGetAgentByIdQuery, useRevertAgentVersionMutation } from '~/data-provider';
-import type { Agent } from 'librechat-data-provider';
+import type { AgentWithVersions, VersionContext } from './types';
import { isActiveVersion } from './isActiveVersion';
import { useAgentPanelContext } from '~/Providers';
import { useLocalize, useToast } from '~/hooks';
import VersionContent from './VersionContent';
import { Panel } from '~/common';
-export type VersionRecord = Record;
-
-export type AgentState = {
- name: string | null;
- description: string | null;
- instructions: string | null;
- artifacts?: string | null;
- capabilities?: string[];
- tools?: string[];
-} | null;
-
-export type VersionWithId = {
- id: number;
- originalIndex: number;
- version: VersionRecord;
- isActive: boolean;
-};
-
-export type VersionContext = {
- versions: VersionRecord[];
- versionIds: VersionWithId[];
- currentAgent: AgentState;
- selectedAgentId: string;
- activeVersion: VersionRecord | null;
-};
-
-export interface AgentWithVersions extends Agent {
- capabilities?: string[];
- versions?: Array;
-}
-
export default function VersionPanel() {
const localize = useLocalize();
const { showToast } = useToast();
diff --git a/client/src/components/SidePanel/Agents/Version/isActiveVersion.ts b/client/src/components/SidePanel/Agents/Version/isActiveVersion.ts
index 61919953d..e0eb5f66d 100644
--- a/client/src/components/SidePanel/Agents/Version/isActiveVersion.ts
+++ b/client/src/components/SidePanel/Agents/Version/isActiveVersion.ts
@@ -1,4 +1,4 @@
-import { AgentState, VersionRecord } from './VersionPanel';
+import type { AgentState, VersionRecord } from './types';
export const isActiveVersion = (
version: VersionRecord,
diff --git a/client/src/components/SidePanel/Agents/Version/types.ts b/client/src/components/SidePanel/Agents/Version/types.ts
new file mode 100644
index 000000000..210e4d32b
--- /dev/null
+++ b/client/src/components/SidePanel/Agents/Version/types.ts
@@ -0,0 +1,35 @@
+export type VersionRecord = Record;
+
+export type AgentState = {
+ name: string | null;
+ description: string | null;
+ instructions: string | null;
+ artifacts?: string | null;
+ capabilities?: string[];
+ tools?: string[];
+} | null;
+
+export type VersionWithId = {
+ id: number;
+ originalIndex: number;
+ version: VersionRecord;
+ isActive: boolean;
+};
+
+export type VersionContext = {
+ versions: VersionRecord[];
+ versionIds: VersionWithId[];
+ currentAgent: AgentState;
+ selectedAgentId: string;
+ activeVersion: VersionRecord | null;
+};
+
+export interface AgentWithVersions {
+ name: string;
+ description: string | null;
+ instructions: string | null;
+ artifacts?: string | null;
+ capabilities?: string[];
+ tools?: string[];
+ versions?: Array;
+}