瀏覽代碼

enh: export table as csv

Co-Authored-By: Muhammad Afzaal <mafzaal@gmail.com>
Timothy J. Baek 6 月之前
父節點
當前提交
70498d7bbe
共有 1 個文件被更改,包括 93 次插入28 次删除
  1. 93 28
      src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte

+ 93 - 28
src/lib/components/chat/Messages/Markdown/MarkdownTokens.svelte

@@ -1,6 +1,11 @@
 <script lang="ts">
 	import DOMPurify from 'dompurify';
-	import { createEventDispatcher, onMount } from 'svelte';
+	import { createEventDispatcher, onMount, getContext } from 'svelte';
+	const i18n = getContext('i18n');
+
+	import fileSaver from 'file-saver';
+	const { saveAs } = fileSaver;
+
 	import { marked, type Token } from 'marked';
 	import { revertSanitizedResponseContent, unescapeHtml } from '$lib/utils';
 
@@ -10,6 +15,8 @@
 	import MarkdownInlineTokens from '$lib/components/chat/Messages/Markdown/MarkdownInlineTokens.svelte';
 	import KatexRenderer from './KatexRenderer.svelte';
 	import Collapsible from '$lib/components/common/Collapsible.svelte';
+	import Tooltip from '$lib/components/common/Tooltip.svelte';
+	import ArrowDownTray from '$lib/components/icons/ArrowDownTray.svelte';
 
 	const dispatch = createEventDispatcher();
 
@@ -22,6 +29,31 @@
 	const headerComponent = (depth: number) => {
 		return 'h' + depth;
 	};
+
+	const exportTableToCSVHandler = (token, tokenIdx = 0) => {
+		console.log('Exporting table to CSV');
+
+		// Create an array for rows that will hold the mapped cell text.
+		const rows = token.rows.map((row) =>
+			row.map((cell) => cell.tokens.map((token) => token.text).join(''))
+		);
+
+		// Join the rows using commas (,) as the separator and rows using newline (\n).
+		const csvContent = rows.map((row) => row.join(',')).join('\n');
+
+		// Log rows and CSV content to ensure everything is correct.
+		console.log(rows);
+		console.log(csvContent);
+
+		// To handle Unicode characters, you need to prefix the data with a BOM:
+		const bom = '\uFEFF'; // BOM for UTF-8
+
+		// Create a new Blob prefixed with the BOM to ensure proper Unicode encoding.
+		const blob = new Blob([bom + csvContent], { type: 'text/csv;charset=UTF-8' });
+
+		// Use FileSaver.js's saveAs function to save the generated CSV file.
+		saveAs(blob, `table-${id}-${tokenIdx}.csv`);
+	};
 </script>
 
 <!-- {JSON.stringify(tokens)} -->
@@ -55,35 +87,68 @@
 			{token.text}
 		{/if}
 	{:else if token.type === 'table'}
-		<div class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full">
-			<table class="w-full">
-				<thead>
-					<tr>
-						{#each token.header as header, headerIdx}
-							<th style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}>
-								<MarkdownInlineTokens
-									id={`${id}-${tokenIdx}-header-${headerIdx}`}
-									tokens={header.tokens}
-								/>
-							</th>
-						{/each}
-					</tr>
-				</thead>
-				<tbody>
-					{#each token.rows as row, rowIdx}
-						<tr>
-							{#each row ?? [] as cell, cellIdx}
-								<td style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}>
-									<MarkdownInlineTokens
-										id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`}
-										tokens={cell.tokens}
-									/>
-								</td>
+		<div class="relative w-full group">
+			<div
+				class="scrollbar-hidden relative whitespace-nowrap overflow-x-auto max-w-full rounded-lg"
+			>
+				<table
+					class="table-auto w-full text-sm text-left text-gray-500 dark:text-gray-400 max-w-full rounded-xl"
+				>
+					<thead
+						class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-850 dark:text-gray-400 border-none"
+					>
+						<tr class="">
+							{#each token.header as header, headerIdx}
+								<th
+									scope="col"
+									class="!px-2 !py-1.5 cursor-pointer select-none border border-gray-50 dark:border-gray-850"
+									style={token.align[headerIdx] ? '' : `text-align: ${token.align[headerIdx]}`}
+								>
+									<div class="flex gap-1.5 items-center">
+										<MarkdownInlineTokens
+											id={`${id}-${tokenIdx}-header-${headerIdx}`}
+											tokens={header.tokens}
+										/>
+									</div>
+								</th>
 							{/each}
 						</tr>
-					{/each}
-				</tbody>
-			</table>
+					</thead>
+					<tbody>
+						{#each token.rows as row, rowIdx}
+							<tr class="bg-white dark:bg-gray-900 dark:border-gray-850 text-xs">
+								{#each row ?? [] as cell, cellIdx}
+									<td
+										class="!px-2 !py-1.5 font-medium text-gray-900 dark:text-white w-max border border-gray-50 dark:border-gray-850"
+										style={token.align[cellIdx] ? '' : `text-align: ${token.align[cellIdx]}`}
+									>
+										<div class="flex">
+											<MarkdownInlineTokens
+												id={`${id}-${tokenIdx}-row-${rowIdx}-${cellIdx}`}
+												tokens={cell.tokens}
+											/>
+										</div>
+									</td>
+								{/each}
+							</tr>
+						{/each}
+					</tbody>
+				</table>
+			</div>
+
+			<div class=" absolute top-1 right-1.5 z-20 invisible group-hover:visible">
+				<Tooltip content={$i18n.t('Export to CSV')}>
+					<button
+						class="p-1 rounded-lg bg-transparent transition"
+						on:click={(e) => {
+							e.stopPropagation();
+							exportTableToCSVHandler(token, tokenIdx);
+						}}
+					>
+						<ArrowDownTray className=" size-3.5" strokeWidth="1.5" />
+					</button>
+				</Tooltip>
+			</div>
 		</div>
 	{:else if token.type === 'blockquote'}
 		<blockquote>