ai-chatbot icon indicating copy to clipboard operation
ai-chatbot copied to clipboard

I want to add icon for exporting to excel near copy icons to export data , How to code?

Open angpao opened this issue 10 months ago • 4 comments

Now , I added markdown for supporting table and I want to add icon export to excel How to code?

angpao avatar Feb 17 '25 17:02 angpao

Hi @angpao , did you find a solution?

yeqiangx avatar Feb 20 '25 09:02 yeqiangx

To add an action, you can add it in the artifacts/text/client.tsx file

For example:

  actions: [
    {
      icon: <FileDown size={18} />,
      description: 'Download',
      onClick: ({ content }) => {
        const blob = new Blob([content], { type: 'text/plain' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = 'document.txt';
        a.click();
        URL.revokeObjectURL(url);
        toast.success('Document downloaded!');
      },
    },

gianpaj avatar Feb 20 '25 12:02 gianpaj

@angpao Were you able to fix this? I'm curious about it too.

shloimy-wiesel avatar Mar 12 '25 19:03 shloimy-wiesel

solution: Added Excel export after table using xlsx.

npm install xlsx
  • markdown.tsx
  table: ({ node, children, ...props }) => {
    return <TableWithExport {...props}>{children}</TableWithExport>
  },
  • table-with-export.tsx
"use client"

import { type ReactNode, useRef } from "react"
import { Button } from "@/components/ui/button"
import { Download } from "lucide-react"
import { exportTableToExcel } from "@/utils/excel-export"

interface TableWithExportProps {
  children: ReactNode
  className?: string
}

export function TableWithExport({ children, className, ...props }: TableWithExportProps) {
  const tableRef = useRef<HTMLTableElement>(null)

  const handleExport = () => {
    if (!tableRef.current) return

    // Extract table data
    const rows = Array.from(tableRef.current.querySelectorAll("tr"))
    const tableData = rows.map((row) =>
      Array.from(row.querySelectorAll("th, td")).map((cell) => cell.textContent || ""),
    )

    // Determine filename from table context
    let filename = "table-export"

    // Try to get filename from table caption if it exists
    const caption = tableRef.current.querySelector("caption")
    if (caption && caption.textContent) {
      filename = caption.textContent.trim()
    }
    // Otherwise, try to get filename from first row (headers)
    else if (tableData.length > 0 && tableData[0].length > 0) {
      // Join the header row with hyphens
      filename = tableData[0].join("-")
    }

    // Look for a preceding heading as an alternative
    if (filename === "table-export") {
      // Get the table's parent element
      const tableParent = tableRef.current.parentElement
      if (tableParent) {
        // Look for the closest preceding heading
        let currentElement = tableParent.previousElementSibling
        while (currentElement) {
          if (/^H[1-6]$/.test(currentElement.tagName)) {
            filename = currentElement.textContent?.trim() || filename
            break
          }
          currentElement = currentElement.previousElementSibling
        }
      }
    }

    // Sanitize filename (remove invalid characters)
    filename = filename
      .replace(/[\\/:*?"<>|]/g, "-") // Replace invalid filename chars
      .replace(/\s+/g, "-") // Replace spaces with hyphens
      .substring(0, 50) // Limit length

    // Export to Excel
    exportTableToExcel(tableData, filename)
  }

  return (
    <div className="relative mb-6">
      <div className="overflow-x-auto rounded-lg border border-zinc-200 dark:border-zinc-700">
        <table ref={tableRef} className={`w-full text-sm ${className || ""}`} {...props}>
          {children}
        </table>
      </div>
      <div className="flex justify-end mt-2">
        <Button onClick={handleExport} size="sm" variant="outline" className="flex items-center gap-1 text-xs">
          <Download className="h-3 w-3" />
          Export to Excel
        </Button>
      </div>
    </div>
  )
}


  • excel-export.ts
import * as XLSX from "xlsx"

export function exportTableToExcel(tableData: string[][], filename = "table-export") {
  // Create a worksheet from the table data
  const ws = XLSX.utils.aoa_to_sheet(tableData)

  // Create a workbook with the worksheet
  const wb = XLSX.utils.book_new()
  XLSX.utils.book_append_sheet(wb, ws, "Sheet1")

  // Generate the Excel file as a binary string
  const excelBuffer = XLSX.write(wb, { bookType: "xlsx", type: "array" })

  // Convert to Blob
  const blob = new Blob([excelBuffer], {
    type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
  })

  // Create download link
  const url = URL.createObjectURL(blob)

  // Create temporary anchor element to trigger download
  const a = document.createElement("a")
  a.href = url
  a.download = `${filename}.xlsx`
  document.body.appendChild(a)
  a.click()

  // Clean up
  setTimeout(() => {
    document.body.removeChild(a)
    URL.revokeObjectURL(url)
  }, 0)
}


shloimy-wiesel avatar Mar 18 '25 09:03 shloimy-wiesel