-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Co-authored-by: Nicolas <[email protected]>
- Loading branch information
1 parent
93b02a8
commit 7680426
Showing
14 changed files
with
419 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { Block } from '@/components/create-block'; | ||
import { | ||
CopyIcon, | ||
LineChartIcon, | ||
RedoIcon, | ||
SparklesIcon, | ||
UndoIcon, | ||
} from '@/components/icons'; | ||
import { SpreadsheetEditor } from '@/components/sheet-editor'; | ||
import { parse, unparse } from 'papaparse'; | ||
import { toast } from 'sonner'; | ||
|
||
type Metadata = any; | ||
|
||
export const sheetBlock = new Block<'sheet', Metadata>({ | ||
kind: 'sheet', | ||
description: 'Useful for working with spreadsheets', | ||
initialize: async () => {}, | ||
onStreamPart: ({ setBlock, streamPart }) => { | ||
if (streamPart.type === 'sheet-delta') { | ||
setBlock((draftBlock) => ({ | ||
...draftBlock, | ||
content: streamPart.content as string, | ||
isVisible: true, | ||
status: 'streaming', | ||
})); | ||
} | ||
}, | ||
content: ({ | ||
content, | ||
currentVersionIndex, | ||
isCurrentVersion, | ||
onSaveContent, | ||
status, | ||
}) => { | ||
return ( | ||
<SpreadsheetEditor | ||
content={content} | ||
currentVersionIndex={currentVersionIndex} | ||
isCurrentVersion={isCurrentVersion} | ||
saveContent={onSaveContent} | ||
status={status} | ||
/> | ||
); | ||
}, | ||
actions: [ | ||
{ | ||
icon: <UndoIcon size={18} />, | ||
description: 'View Previous version', | ||
onClick: ({ handleVersionChange }) => { | ||
handleVersionChange('prev'); | ||
}, | ||
isDisabled: ({ currentVersionIndex }) => { | ||
if (currentVersionIndex === 0) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}, | ||
}, | ||
{ | ||
icon: <RedoIcon size={18} />, | ||
description: 'View Next version', | ||
onClick: ({ handleVersionChange }) => { | ||
handleVersionChange('next'); | ||
}, | ||
isDisabled: ({ isCurrentVersion }) => { | ||
if (isCurrentVersion) { | ||
return true; | ||
} | ||
|
||
return false; | ||
}, | ||
}, | ||
{ | ||
icon: <CopyIcon />, | ||
description: 'Copy as .csv', | ||
onClick: ({ content }) => { | ||
const parsed = parse<string[]>(content, { skipEmptyLines: true }); | ||
|
||
const nonEmptyRows = parsed.data.filter((row) => | ||
row.some((cell) => cell.trim() !== ''), | ||
); | ||
|
||
const cleanedCsv = unparse(nonEmptyRows); | ||
|
||
navigator.clipboard.writeText(cleanedCsv); | ||
toast.success('Copied csv to clipboard!'); | ||
}, | ||
}, | ||
], | ||
toolbar: [ | ||
{ | ||
description: 'Format and clean data', | ||
icon: <SparklesIcon />, | ||
onClick: ({ appendMessage }) => { | ||
appendMessage({ | ||
role: 'user', | ||
content: 'Can you please format and clean the data?', | ||
}); | ||
}, | ||
}, | ||
{ | ||
description: 'Analyze and visualize data', | ||
icon: <LineChartIcon />, | ||
onClick: ({ appendMessage }) => { | ||
appendMessage({ | ||
role: 'user', | ||
content: | ||
'Can you please analyze and visualize the data by creating a new code block in python?', | ||
}); | ||
}, | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
'use client'; | ||
|
||
import React, { memo, useEffect, useMemo, useState } from 'react'; | ||
import DataGrid, { textEditor } from 'react-data-grid'; | ||
import { parse, unparse } from 'papaparse'; | ||
import { useTheme } from 'next-themes'; | ||
import { cn } from '@/lib/utils'; | ||
|
||
import 'react-data-grid/lib/styles.css'; | ||
|
||
type SheetEditorProps = { | ||
content: string; | ||
saveContent: (content: string, isCurrentVersion: boolean) => void; | ||
status: string; | ||
isCurrentVersion: boolean; | ||
currentVersionIndex: number; | ||
}; | ||
|
||
const MIN_ROWS = 50; | ||
const MIN_COLS = 26; | ||
|
||
const PureSpreadsheetEditor = ({ | ||
content, | ||
saveContent, | ||
status, | ||
isCurrentVersion, | ||
}: SheetEditorProps) => { | ||
const { theme } = useTheme(); | ||
|
||
const parseData = useMemo(() => { | ||
if (!content) return Array(MIN_ROWS).fill(Array(MIN_COLS).fill('')); | ||
const result = parse<string[]>(content, { skipEmptyLines: true }); | ||
|
||
const paddedData = result.data.map((row) => { | ||
const paddedRow = [...row]; | ||
while (paddedRow.length < MIN_COLS) { | ||
paddedRow.push(''); | ||
} | ||
return paddedRow; | ||
}); | ||
|
||
while (paddedData.length < MIN_ROWS) { | ||
paddedData.push(Array(MIN_COLS).fill('')); | ||
} | ||
|
||
return paddedData; | ||
}, [content]); | ||
|
||
const columns = useMemo(() => { | ||
const rowNumberColumn = { | ||
key: 'rowNumber', | ||
name: '', | ||
frozen: true, | ||
width: 50, | ||
renderCell: ({ rowIdx }: { rowIdx: number }) => rowIdx + 1, | ||
cellClass: 'border-t border-r dark:bg-zinc-950 dark:text-zinc-50', | ||
headerCellClass: 'border-t border-r dark:bg-zinc-900 dark:text-zinc-50', | ||
}; | ||
|
||
const dataColumns = Array.from({ length: MIN_COLS }, (_, i) => ({ | ||
key: i.toString(), | ||
name: String.fromCharCode(65 + i), | ||
renderEditCell: textEditor, | ||
width: 120, | ||
cellClass: cn(`border-t dark:bg-zinc-950 dark:text-zinc-50`, { | ||
'border-l': i !== 0, | ||
}), | ||
headerCellClass: cn(`border-t dark:bg-zinc-900 dark:text-zinc-50`, { | ||
'border-l': i !== 0, | ||
}), | ||
})); | ||
|
||
return [rowNumberColumn, ...dataColumns]; | ||
}, []); | ||
|
||
const initialRows = useMemo(() => { | ||
return parseData.map((row, rowIndex) => { | ||
const rowData: any = { | ||
id: rowIndex, | ||
rowNumber: rowIndex + 1, | ||
}; | ||
|
||
columns.slice(1).forEach((col, colIndex) => { | ||
rowData[col.key] = row[colIndex] || ''; | ||
}); | ||
|
||
return rowData; | ||
}); | ||
}, [parseData, columns]); | ||
|
||
const [localRows, setLocalRows] = useState(initialRows); | ||
|
||
useEffect(() => { | ||
setLocalRows(initialRows); | ||
}, [initialRows]); | ||
|
||
const generateCsv = (data: any[][]) => { | ||
return unparse(data); | ||
}; | ||
|
||
const handleRowsChange = (newRows: any[]) => { | ||
setLocalRows(newRows); | ||
|
||
const updatedData = newRows.map((row) => { | ||
return columns.slice(1).map((col) => row[col.key] || ''); | ||
}); | ||
|
||
const newCsvContent = generateCsv(updatedData); | ||
saveContent(newCsvContent, true); | ||
}; | ||
|
||
return ( | ||
<DataGrid | ||
className={theme === 'dark' ? 'rdg-dark' : 'rdg-light'} | ||
columns={columns} | ||
rows={localRows} | ||
enableVirtualization | ||
onRowsChange={handleRowsChange} | ||
onCellClick={(args) => { | ||
if (args.column.key !== 'rowNumber') { | ||
args.selectCell(true); | ||
} | ||
}} | ||
style={{ height: '100%' }} | ||
defaultColumnOptions={{ | ||
resizable: true, | ||
sortable: true, | ||
}} | ||
/> | ||
); | ||
}; | ||
|
||
function areEqual(prevProps: SheetEditorProps, nextProps: SheetEditorProps) { | ||
return ( | ||
prevProps.currentVersionIndex === nextProps.currentVersionIndex && | ||
prevProps.isCurrentVersion === nextProps.isCurrentVersion && | ||
!(prevProps.status === 'streaming' && nextProps.status === 'streaming') && | ||
prevProps.content === nextProps.content && | ||
prevProps.saveContent === nextProps.saveContent | ||
); | ||
} | ||
|
||
export const SpreadsheetEditor = memo(PureSpreadsheetEditor, areEqual); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,6 +33,7 @@ import { | |
LogsIcon, | ||
MessageIcon, | ||
PenIcon, | ||
SparklesIcon, | ||
StopIcon, | ||
SummarizeIcon, | ||
} from './icons'; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.