Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export Tables as CSV #392

Merged
merged 3 commits into from
Feb 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
82 changes: 77 additions & 5 deletions src/apps/chat/components/message/RenderMarkdown.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import * as React from 'react';

import { Box, styled } from '@mui/joy';
import { CSVDownload, CSVLink } from 'react-csv';

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';

/*
* For performance reasons, we style this component here and copy the equivalent of 'props.sx' (the lineHeight) locally.
Expand All @@ -32,14 +35,83 @@ 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) =>
<markdownModule.default remarkPlugins={remarkPlugins} {...props} />;
//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 (
<>
<table style={{ borderCollapse: 'collapse', width: '100%' }} {...props}>
{children}
</table>
<CSVLink filename='big-agi-export' data={tableData}>
<Button variant="outlined">
<DownloadIcon />
{'Download table as .csv '}
</Button>
</CSVLink>
</>
);
};

// 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) =>
<markdownModule.default
remarkPlugins={remarkPlugins}
{...props}
components={components}
/>;

return { default: ReactMarkdownWithRemarkGfm };
});


export const RenderMarkdown = (props: { textBlock: TextBlock }) => {
return (
<RenderMarkdownBox className='markdown-body' /* NODE: see GithubMarkdown.css for the dark/light switch, synced with Joy's */ >
Expand Down