diff --git a/package.json b/package.json index cf998d0..7bf93d3 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "i18next-http-backend": "^2.1.1", "jspdf": "^2.5.1", "match-sorter": "^6.3.1", + "papaparse": "^5.4.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^12.2.0", @@ -59,6 +60,7 @@ }, "devDependencies": { "@tailwindcss/typography": "^0.5.9", + "@types/papaparse": "^5.3.7", "@types/react": "^18.0.27", "@types/react-dom": "^18.0.10", "@types/react-scroll-to-bottom": "^4.2.0", diff --git a/src/components/PromptLibraryMenu/ExportPrompt.tsx b/src/components/PromptLibraryMenu/ExportPrompt.tsx new file mode 100644 index 0000000..366eb81 --- /dev/null +++ b/src/components/PromptLibraryMenu/ExportPrompt.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import useStore from '@store/store'; +import { exportPrompts } from '@utils/prompt'; + +const ExportPrompt = () => { + const { t } = useTranslation(); + const prompts = useStore.getState().prompts; + + return ( +
+
+ {t('export')} +
+ +
+ ); +}; + +export default ExportPrompt; diff --git a/src/components/PromptLibraryMenu/ImportPrompt.tsx b/src/components/PromptLibraryMenu/ImportPrompt.tsx new file mode 100644 index 0000000..661c99c --- /dev/null +++ b/src/components/PromptLibraryMenu/ImportPrompt.tsx @@ -0,0 +1,84 @@ +import React, { useRef, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { v4 as uuidv4 } from 'uuid'; +import useStore from '@store/store'; + +import { importPromptCSV } from '@utils/prompt'; + +const ImportPrompt = () => { + const { t } = useTranslation(); + + const inputRef = useRef(null); + const [alert, setAlert] = useState<{ + message: string; + success: boolean; + } | null>(null); + + const handleFileUpload = () => { + if (!inputRef || !inputRef.current) return; + const file = inputRef.current.files?.[0]; + if (file) { + const reader = new FileReader(); + + reader.onload = (event) => { + const csvString = event.target?.result as string; + + try { + const results = importPromptCSV(csvString); + + const prompts = useStore.getState().prompts; + const setPrompts = useStore.getState().setPrompts; + + const newPrompts = results.map((data) => { + const columns = Object.values(data); + return { + id: uuidv4(), + name: columns[0], + prompt: columns[1], + }; + }); + + setPrompts(prompts.concat(newPrompts)); + + setAlert({ message: 'Succesfully imported!', success: true }); + } catch (error: unknown) { + setAlert({ message: (error as Error).message, success: false }); + } + }; + + reader.readAsText(file); + } + }; + + return ( +
+ + + + {alert && ( +
+ {alert.message} +
+ )} +
+ ); +}; + +export default ImportPrompt; diff --git a/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx b/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx index 568c431..f7d2d52 100644 --- a/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx +++ b/src/components/PromptLibraryMenu/PromptLibraryMenu.tsx @@ -7,6 +7,8 @@ import { Prompt } from '@type/prompt'; import PlusIcon from '@icon/PlusIcon'; import CrossIcon from '@icon/CrossIcon'; import { v4 as uuidv4 } from 'uuid'; +import ImportPrompt from './ImportPrompt'; +import ExportPrompt from './ExportPrompt'; const PromptLibraryMenu = () => { const { t } = useTranslation(); @@ -31,8 +33,10 @@ const PromptLibraryMenuPopUp = ({ const { t } = useTranslation(); const setPrompts = useStore((state) => state.setPrompts); + const prompts = useStore((state) => state.prompts); + const [_prompts, _setPrompts] = useState( - JSON.parse(JSON.stringify(useStore.getState().prompts)) + JSON.parse(JSON.stringify(prompts)) ); const container = useRef(null); @@ -74,6 +78,10 @@ const PromptLibraryMenuPopUp = ({ e.target.style.maxHeight = '2.5rem'; }; + useEffect(() => { + _setPrompts(prompts); + }, [prompts]); + return (
+
+ + +
{t('name')}
diff --git a/src/utils/prompt.ts b/src/utils/prompt.ts new file mode 100644 index 0000000..e78ceb1 --- /dev/null +++ b/src/utils/prompt.ts @@ -0,0 +1,31 @@ +import { Prompt } from '@type/prompt'; +import { getToday } from './date'; + +import Papa from 'papaparse'; + +export const importPromptCSV = (csvString: string, header: boolean = true) => { + const results = Papa.parse(csvString, { + header, + delimiter: ',', + newline: '\n', + skipEmptyLines: true, + }); + + return results.data as Record[]; +}; + +export const exportPrompts = (prompts: Prompt[]) => { + const csvString = Papa.unparse( + prompts.map((prompt) => ({ name: prompt.name, prompt: prompt.prompt })) + ); + + const blob = new Blob([csvString], { + type: 'text/csv;charset=utf-8;', + }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `${getToday()}.csv`; + link.click(); + link.remove(); +}; diff --git a/yarn.lock b/yarn.lock index 7a86cde..79e8583 100644 --- a/yarn.lock +++ b/yarn.lock @@ -609,6 +609,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.23.tgz#b6e934fe427eb7081d0015aad070acb3373c3c90" integrity sha512-XAMpaw1s1+6zM+jn2tmw8MyaRDIJfXxqmIQIS0HfoGYPuf7dUWeiUKopwq13KFX9lEp1+THGtlaaYx39Nxr58g== +"@types/papaparse@^5.3.7": + version "5.3.7" + resolved "https://registry.yarnpkg.com/@types/papaparse/-/papaparse-5.3.7.tgz#8d3bf9e62ac2897df596f49d9ca59a15451aa247" + integrity sha512-f2HKmlnPdCvS0WI33WtCs5GD7X1cxzzS/aduaxSu3I7TbhWlENjSPs6z5TaB9K0J+BH1jbmqTaM+ja5puis4wg== + dependencies: + "@types/node" "*" + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2992,6 +2999,11 @@ p-cancelable@^2.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== +papaparse@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/papaparse/-/papaparse-5.4.1.tgz#f45c0f871853578bd3a30f92d96fdcfb6ebea127" + integrity sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"