mirror of
https://github.com/NovaOSS/nova-betterchat.git
synced 2024-11-25 19:43:59 +01:00
parent
2c89c9325b
commit
e14f44b8c9
|
@ -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",
|
||||
|
|
27
src/components/PromptLibraryMenu/ExportPrompt.tsx
Normal file
27
src/components/PromptLibraryMenu/ExportPrompt.tsx
Normal file
|
@ -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 (
|
||||
<div className='mt-4'>
|
||||
<div className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||
{t('export')}
|
||||
</div>
|
||||
<button
|
||||
className='btn btn-small btn-primary'
|
||||
onClick={() => {
|
||||
exportPrompts(prompts);
|
||||
}}
|
||||
>
|
||||
{t('export')}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExportPrompt;
|
84
src/components/PromptLibraryMenu/ImportPrompt.tsx
Normal file
84
src/components/PromptLibraryMenu/ImportPrompt.tsx
Normal file
|
@ -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<HTMLInputElement>(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 (
|
||||
<div>
|
||||
<label className='block mb-2 text-sm font-medium text-gray-900 dark:text-gray-300'>
|
||||
{t('import')}
|
||||
</label>
|
||||
<input
|
||||
className='w-full text-sm file:p-2 text-gray-800 file:text-gray-700 dark:text-gray-300 dark:file:text-gray-200 rounded-md cursor-pointer focus:outline-none bg-gray-50 file:bg-gray-100 dark:bg-gray-800 dark:file:bg-gray-700 file:border-0 border border-gray-300 dark:border-gray-600 placeholder-gray-900 dark:placeholder-gray-300 file:cursor-pointer'
|
||||
type='file'
|
||||
ref={inputRef}
|
||||
/>
|
||||
<button
|
||||
className='btn btn-small btn-primary mt-3'
|
||||
onClick={handleFileUpload}
|
||||
>
|
||||
{t('import')}
|
||||
</button>
|
||||
{alert && (
|
||||
<div
|
||||
className={`relative py-2 px-3 w-full mt-3 border rounded-md text-gray-600 dark:text-gray-100 text-sm whitespace-pre-wrap ${
|
||||
alert.success
|
||||
? 'border-green-500 bg-green-500/10'
|
||||
: 'border-red-500 bg-red-500/10'
|
||||
}`}
|
||||
>
|
||||
{alert.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportPrompt;
|
|
@ -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<Prompt[]>(
|
||||
JSON.parse(JSON.stringify(useStore.getState().prompts))
|
||||
JSON.parse(JSON.stringify(prompts))
|
||||
);
|
||||
const container = useRef<HTMLDivElement>(null);
|
||||
|
||||
|
@ -74,6 +78,10 @@ const PromptLibraryMenuPopUp = ({
|
|||
e.target.style.maxHeight = '2.5rem';
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
_setPrompts(prompts);
|
||||
}, [prompts]);
|
||||
|
||||
return (
|
||||
<PopupModal
|
||||
title={t('promptLibrary') as string}
|
||||
|
@ -81,6 +89,10 @@ const PromptLibraryMenuPopUp = ({
|
|||
handleConfirm={handleSave}
|
||||
>
|
||||
<div className='p-6 border-b border-gray-200 dark:border-gray-600 w-[90vw] max-w-full text-sm text-gray-900 dark:text-gray-300'>
|
||||
<div className='border px-4 py-2 rounded border-gray-200 dark:border-gray-600'>
|
||||
<ImportPrompt />
|
||||
<ExportPrompt />
|
||||
</div>
|
||||
<div className='flex flex-col p-2 max-w-full' ref={container}>
|
||||
<div className='flex font-bold border-b border-gray-500/50 mb-1 p-1'>
|
||||
<div className='sm:w-1/4 max-sm:flex-1'>{t('name')}</div>
|
||||
|
|
31
src/utils/prompt.ts
Normal file
31
src/utils/prompt.ts
Normal file
|
@ -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<string, string>[];
|
||||
};
|
||||
|
||||
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();
|
||||
};
|
12
yarn.lock
12
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"
|
||||
|
|
Loading…
Reference in a new issue