File size: 2,403 Bytes
05c5ed5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import { Buffer } from "node:buffer";

export type CsvPreview = {
  header: string[];
  rows: string[][]; // limited by maxRows/maxCols
  columns: number;
  totalRows: number;
  markdownTable: string; // simple markdown table for the chat
};

export function parseCsvPreview(
  content: Buffer,
  opts: { maxRows?: number; maxCols?: number } = {},
): CsvPreview {
  const maxRows = Math.max(1, opts.maxRows ?? 50);
  const maxCols = Math.max(1, opts.maxCols ?? 12);
  const text = content.toString("utf8");

  const rows: string[][] = [];
  let i = 0;
  let field = "";
  let inQuotes = false;
  let row: string[] = [];

  const pushField = () => {
    row.push(field);
    field = "";
  };
  const pushRow = () => {
    rows.push(row);
    row = [];
  };

  while (i < text.length) {
    const ch = text[i++];
    if (inQuotes) {
      if (ch === '"') {
        if (text[i] === '"') {
          field += '"';
          i++;
        } else {
          inQuotes = false;
        }
      } else {
        field += ch;
      }
    } else {
      if (ch === '"') {
        inQuotes = true;
      } else if (ch === ",") {
        pushField();
      } else if (ch === "\n") {
        pushField();
        pushRow();
      } else if (ch === "\r") {
        // ignore CR (handle CRLF)
      } else {
        field += ch;
      }
    }
  }
  // flush last field/row
  pushField();
  if (row.some(field => field !== "")) pushRow();

  const totalRows = rows.length;
  const header = rows[0] ?? [];
  const limitedHeader = header.slice(0, maxCols);
  const dataRows = rows.slice(1);
  const limitedRows = dataRows
    .slice(0, maxRows)
    .map((r) => r.slice(0, maxCols));
  const columns = limitedHeader.length;

  const mdHeader = `| ${limitedHeader.join(" | ")} |`;
  const mdSep = `| ${limitedHeader.map(() => "---").join(" | ")} |`;
  const mdBody = limitedRows
    .map(
      (r) => `| ${r.map((c) => (c ?? "").replace(/\|/g, "\\|")).join(" | ")} |`,
    )
    .join("\n");
  const markdownTable = [mdHeader, mdSep, mdBody].join("\n");

  return {
    header: limitedHeader,
    rows: limitedRows,
    columns,
    totalRows,
    markdownTable,
  };
}

export const formatCsvPreviewText = (
  name: string,
  preview: CsvPreview,
): string => {
  return `Here is a preview of ${name} (rows: ${preview.totalRows}, cols: ${preview.columns}). Summarize or analyze as needed.\n\n${preview.markdownTable}`;
};