From ce00480d99ae07dcdbe6288c3b37fce094c4bd0a Mon Sep 17 00:00:00 2001 From: Arash Joobandi Date: Sun, 4 Feb 2024 15:31:41 +1100 Subject: [PATCH 1/3] add download csv button --- .../components/message/RenderMarkdown.tsx | 66 +++++++++++++++++-- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/src/apps/chat/components/message/RenderMarkdown.tsx b/src/apps/chat/components/message/RenderMarkdown.tsx index 0ea27ae52d..8bec799cb6 100644 --- a/src/apps/chat/components/message/RenderMarkdown.tsx +++ b/src/apps/chat/components/message/RenderMarkdown.tsx @@ -1,11 +1,40 @@ import * as React from 'react'; -import { Box, styled } from '@mui/joy'; +import { Box, Button, styled } from '@mui/joy'; import { lineHeightChatText } from '~/common/app.theme'; import type { TextBlock } from './blocks'; +import DownloadIcon from '@mui/icons-material/Download'; + +const extractMarkdownTables = (input: string): string[][][] => { + // Split the input into sections based on lines that start and end with pipes, which might indicate tables + const potentialTables = input.split('\n\n').filter((section) => section.includes('|')); + const tables: string[][][] = []; + + potentialTables.forEach((section) => { + // Split the section into rows and filter out non-table rows and header separators + const rows = section.split('\n').filter((row) => { + return row.trim().startsWith('|') && row.trim().endsWith('|') && !row.trim().match(/^\|[-:| ]+\|$/); + }); + + if (rows.length > 0) { + // Process each row to split into cells, trimming whitespace + const tableData = rows.map( + (row) => + row + .split('|') + .slice(1, -1) + .map((cell) => cell.trim()), // Remove the first and last empty cells resulting from split + ); + + tables.push(tableData); + } + }); + + return tables; +}; /* * For performance reasons, we style this component here and copy the equivalent of 'props.sx' (the lineHeight) locally. @@ -32,15 +61,42 @@ const DynamicReactGFM = React.lazy(async () => { // NOTE: extracted here instead of inline as a large performance optimization const remarkPlugins = [remarkGfmModule.default]; - // Pass the dynamically imported remarkGfm as children - const ReactMarkdownWithRemarkGfm = (props: any) => - ; + interface TableRendererProps { + children: React.JSX.Element; + } + // Define a custom table renderer + const TableRenderer = ({ children, ...props }: TableRendererProps) => { + // Apply custom styles or modifications here + return ( + + {children} + +
+ ); + }; + + // Use the custom renderer for tables + const components = { + table: TableRenderer, + // Add custom renderers for other elements if needed + }; + // Pass the dynamically imported remarkGfm as children + const ReactMarkdownWithRemarkGfm = (props: any) => + ; + return { default: ReactMarkdownWithRemarkGfm }; }); - export const RenderMarkdown = (props: { textBlock: TextBlock }) => { + extractMarkdownTables(props.textBlock.content); return ( Loading...}> From 3ad350b10bcf178b84ebf17e56416e01f5083375 Mon Sep 17 00:00:00 2001 From: Arash Joobandi Date: Sun, 4 Feb 2024 15:49:47 +1100 Subject: [PATCH 2/3] implement react-csv download --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index dabf1d7642..8cbc304bea 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "prismjs": "^1.29.0", "react": "^18.2.0", "react-beautiful-dnd": "^13.1.1", + "react-csv": "^2.2.2", "react-dom": "^18.2.0", "react-katex": "^3.0.1", "react-markdown": "^9.0.1", @@ -61,6 +62,7 @@ "@types/prismjs": "^1.26.3", "@types/react": "^18.2.51", "@types/react-beautiful-dnd": "^13.1.8", + "@types/react-csv": "^1.1.10", "@types/react-dom": "^18.2.18", "@types/react-katex": "^3.0.4", "@types/react-timeago": "^4.1.7", From 8d3377aeb3ac59f3d767e838f48b328e9704725d Mon Sep 17 00:00:00 2001 From: Arash Joobandi Date: Sun, 4 Feb 2024 15:51:21 +1100 Subject: [PATCH 3/3] misssing commit --- .../components/message/RenderMarkdown.tsx | 88 +++++++++++-------- 1 file changed, 52 insertions(+), 36 deletions(-) diff --git a/src/apps/chat/components/message/RenderMarkdown.tsx b/src/apps/chat/components/message/RenderMarkdown.tsx index 8bec799cb6..61f5628053 100644 --- a/src/apps/chat/components/message/RenderMarkdown.tsx +++ b/src/apps/chat/components/message/RenderMarkdown.tsx @@ -1,5 +1,7 @@ import * as React from 'react'; +import { CSVDownload, CSVLink } from 'react-csv'; + import { Box, Button, styled } from '@mui/joy'; import { lineHeightChatText } from '~/common/app.theme'; @@ -8,34 +10,6 @@ import type { TextBlock } from './blocks'; import DownloadIcon from '@mui/icons-material/Download'; -const extractMarkdownTables = (input: string): string[][][] => { - // Split the input into sections based on lines that start and end with pipes, which might indicate tables - const potentialTables = input.split('\n\n').filter((section) => section.includes('|')); - const tables: string[][][] = []; - - potentialTables.forEach((section) => { - // Split the section into rows and filter out non-table rows and header separators - const rows = section.split('\n').filter((row) => { - return row.trim().startsWith('|') && row.trim().endsWith('|') && !row.trim().match(/^\|[-:| ]+\|$/); - }); - - if (rows.length > 0) { - // Process each row to split into cells, trimming whitespace - const tableData = rows.map( - (row) => - row - .split('|') - .slice(1, -1) - .map((cell) => cell.trim()), // Remove the first and last empty cells resulting from split - ); - - tables.push(tableData); - } - }); - - return tables; -}; - /* * For performance reasons, we style this component here and copy the equivalent of 'props.sx' (the lineHeight) locally. */ @@ -61,20 +35,63 @@ const DynamicReactGFM = React.lazy(async () => { // NOTE: extracted here instead of inline as a large performance optimization const remarkPlugins = [remarkGfmModule.default]; + //Extracts table data from jsx element in table renderer + const extractTableData = (children: React.JSX.Element) => { + // Function to extract text from a React element or component + const extractText = (element: any): String => { + // Base case: if the element is a string, return it + if (typeof element === 'string') { + return element; + } + // If the element has children, recursively extract text from them + if (element.props && element.props.children) { + if (Array.isArray(element.props.children)) { + return element.props.children.map(extractText).join(''); + } + return extractText(element.props.children); + } + return ''; + }; + + // Function to traverse and extract data from table rows and cells + const traverseAndExtract = (elements: any, tableData: any[] = []) => { + React.Children.forEach(elements, (element) => { + if (element.type === 'tr') { + const rowData = React.Children.map(element.props.children, (cell) => { + // Extract and return the text content of each cell + return extractText(cell); + }); + tableData.push(rowData); + } else if (element.props && element.props.children) { + traverseAndExtract(element.props.children, tableData); + } + }); + return tableData; + }; + + return traverseAndExtract(children); + }; + interface TableRendererProps { children: React.JSX.Element; } // Define a custom table renderer const TableRenderer = ({ children, ...props }: TableRendererProps) => { // Apply custom styles or modifications here + const tableData = extractTableData(children); + return ( - - {children} - -
+ <> + + {children} +
+ + + + ); }; @@ -96,7 +113,6 @@ const DynamicReactGFM = React.lazy(async () => { }); export const RenderMarkdown = (props: { textBlock: TextBlock }) => { - extractMarkdownTables(props.textBlock.content); return ( Loading...}>