mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-17 17:00:15 +01:00
Merge pull request #168 from danny-avila/feat-export-convo
fix: remove use-screenshot
This commit is contained in:
commit
7ec90a3585
4 changed files with 81 additions and 58 deletions
|
|
@ -68,7 +68,6 @@
|
||||||
"tailwindcss-animate": "^1.0.5",
|
"tailwindcss-animate": "^1.0.5",
|
||||||
"tailwindcss-radix": "^2.8.0",
|
"tailwindcss-radix": "^2.8.0",
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"use-react-screenshot": "github:danny-avila/use-react-screenshot#master",
|
|
||||||
"uuidv4": "^6.2.13"
|
"uuidv4": "^6.2.13"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@ export default function ExportModel({ open, onOpenChange }) {
|
||||||
|
|
||||||
const [includeOptions, setIncludeOptions] = useState(true);
|
const [includeOptions, setIncludeOptions] = useState(true);
|
||||||
const [exportBranches, setExportBranches] = useState(false);
|
const [exportBranches, setExportBranches] = useState(false);
|
||||||
const [exportBranchesSupport, setExportBranchesSupport] = useState(false);
|
|
||||||
const [recursive, setRecursive] = useState(true);
|
const [recursive, setRecursive] = useState(true);
|
||||||
|
|
||||||
const conversation = useRecoilValue(store.conversation) || {};
|
const conversation = useRecoilValue(store.conversation) || {};
|
||||||
|
|
@ -37,30 +36,34 @@ export default function ExportModel({ open, onOpenChange }) {
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const typeOptions = ['text', 'markdown', 'csv', 'json', 'screenshot']; //,, 'webpage'];
|
const typeOptions = [
|
||||||
|
{ value: 'text', display: 'text (.txt)' },
|
||||||
|
{ value: 'markdown', display: 'markdown (.md)' },
|
||||||
|
{ value: 'csv', display: 'csv (.csv)' },
|
||||||
|
{ value: 'json', display: 'json (.json)' },
|
||||||
|
{ value: 'screenshot', display: 'screenshot (.png)' }
|
||||||
|
]; //,, 'webpage'];
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFileName(
|
setFileName(filenamify(String(conversation?.title || 'file')));
|
||||||
filenamify(String(conversation?.title || 'file'))
|
|
||||||
);
|
|
||||||
setType('text');
|
setType('text');
|
||||||
setIncludeOptions(true);
|
setIncludeOptions(true);
|
||||||
setExportBranches(false);
|
setExportBranches(false);
|
||||||
setExportBranchesSupport(false);
|
|
||||||
setRecursive(true);
|
setRecursive(true);
|
||||||
}, [open]);
|
}, [open]);
|
||||||
|
|
||||||
const _setType = newType => {
|
const _setType = newType => {
|
||||||
if (newType === 'json' || newType === 'csv' || newType === 'webpage') {
|
const exportBranchesSupport = newType === 'json' || newType === 'csv' || newType === 'webpage';
|
||||||
setExportBranches(true);
|
const exportOptionsSupport = newType !== 'csv' && newType !== 'screenshot';
|
||||||
setExportBranchesSupport(true);
|
|
||||||
} else {
|
setExportBranches(exportBranchesSupport);
|
||||||
setExportBranches(false);
|
setIncludeOptions(exportOptionsSupport);
|
||||||
setExportBranchesSupport(false);
|
|
||||||
}
|
|
||||||
setType(newType);
|
setType(newType);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const exportBranchesSupport = type === 'json' || type === 'csv' || type === 'webpage';
|
||||||
|
const exportOptionsSupport = type !== 'csv' && type !== 'screenshot';
|
||||||
|
|
||||||
// return an object or an array based on branches and recursive option
|
// return an object or an array based on branches and recursive option
|
||||||
// messageId is used to get siblindIdx from recoil snapshot
|
// messageId is used to get siblindIdx from recoil snapshot
|
||||||
const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => {
|
const buildMessageTree = async ({ messageId, message, messages, branches = false, recursive = false }) => {
|
||||||
|
|
@ -320,57 +323,54 @@ export default function ExportModel({ open, onOpenChange }) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid w-full gap-6 sm:grid-cols-2">
|
<div className="grid w-full gap-6 sm:grid-cols-2">
|
||||||
{type !== 'csv' && type !== 'screenshot' ? (
|
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
|
||||||
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
|
|
||||||
<div className="grid w-full items-center gap-2">
|
|
||||||
<Label
|
|
||||||
htmlFor="includeOptions"
|
|
||||||
className="text-left text-sm font-medium"
|
|
||||||
>
|
|
||||||
Include endpoint options
|
|
||||||
</Label>
|
|
||||||
<div className="flex h-[40px] w-full items-center space-x-3">
|
|
||||||
<Checkbox
|
|
||||||
id="includeOptions"
|
|
||||||
checked={includeOptions}
|
|
||||||
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
|
||||||
onCheckedChange={setIncludeOptions}
|
|
||||||
/>
|
|
||||||
<label
|
|
||||||
htmlFor="includeOptions"
|
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
|
||||||
>
|
|
||||||
Enabled
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{type !== 'screenshot' ? (
|
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label
|
<Label
|
||||||
htmlFor="exportBranches"
|
htmlFor="includeOptions"
|
||||||
className="text-left text-sm font-medium"
|
className="text-left text-sm font-medium"
|
||||||
>
|
>
|
||||||
Export all message branches
|
Include endpoint options
|
||||||
</Label>
|
</Label>
|
||||||
<div className="flex h-[40px] w-full items-center space-x-3">
|
<div className="flex h-[40px] w-full items-center space-x-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="exportBranches"
|
id="includeOptions"
|
||||||
disabled={!exportBranchesSupport}
|
disabled={!exportOptionsSupport}
|
||||||
checked={exportBranches}
|
checked={includeOptions}
|
||||||
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
||||||
onCheckedChange={setExportBranches}
|
onCheckedChange={setIncludeOptions}
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="exportBranches"
|
htmlFor="includeOptions"
|
||||||
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
||||||
>
|
>
|
||||||
{exportBranchesSupport ? 'Enabled' : 'Not Supported'}
|
{exportOptionsSupport ? 'Enabled' : 'Not Supported'}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
</div>
|
||||||
|
<div className="grid w-full items-center gap-2">
|
||||||
|
<Label
|
||||||
|
htmlFor="exportBranches"
|
||||||
|
className="text-left text-sm font-medium"
|
||||||
|
>
|
||||||
|
Export all message branches
|
||||||
|
</Label>
|
||||||
|
<div className="flex h-[40px] w-full items-center space-x-3">
|
||||||
|
<Checkbox
|
||||||
|
id="exportBranches"
|
||||||
|
disabled={!exportBranchesSupport}
|
||||||
|
checked={exportBranches}
|
||||||
|
className="focus:ring-opacity-20 dark:border-gray-500 dark:bg-gray-700 dark:text-gray-50 dark:focus:ring-gray-600 dark:focus:ring-opacity-50 dark:focus:ring-offset-0"
|
||||||
|
onCheckedChange={setExportBranches}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor="exportBranches"
|
||||||
|
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
|
||||||
|
>
|
||||||
|
{exportBranchesSupport ? 'Enabled' : 'Not Supported'}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{type === 'json' ? (
|
{type === 'json' ? (
|
||||||
<div className="grid w-full items-center gap-2">
|
<div className="grid w-full items-center gap-2">
|
||||||
<Label
|
<Label
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { Listbox } from '@headlessui/react';
|
||||||
import { cn } from '~/utils/';
|
import { cn } from '~/utils/';
|
||||||
|
|
||||||
function Dropdown({ value, onChange, options, className, containerClassName }) {
|
function Dropdown({ value, onChange, options, className, containerClassName }) {
|
||||||
|
const currentOption = options.find(element => element === value || element?.value === value) ?? value;
|
||||||
return (
|
return (
|
||||||
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
|
<div className={cn('flex items-center justify-center gap-2', containerClassName)}>
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
|
|
@ -19,7 +20,7 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
|
||||||
>
|
>
|
||||||
<span className="inline-flex w-full truncate">
|
<span className="inline-flex w-full truncate">
|
||||||
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
|
<span className="flex h-6 items-center gap-1 truncate text-sm text-black dark:text-white">
|
||||||
{value}
|
{currentOption?.display ?? value}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
||||||
|
|
@ -43,19 +44,19 @@ function Dropdown({ value, onChange, options, className, containerClassName }) {
|
||||||
{options.map((item, i) => (
|
{options.map((item, i) => (
|
||||||
<Listbox.Option
|
<Listbox.Option
|
||||||
key={i}
|
key={i}
|
||||||
value={item}
|
value={item?.value ?? item}
|
||||||
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-[#ECECF1] dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
className="group relative flex h-[42px] cursor-pointer select-none items-center overflow-hidden border-b border-black/10 pl-3 pr-9 text-gray-900 last:border-0 hover:bg-[#ECECF1] dark:border-white/20 dark:text-white dark:hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-1.5 truncate">
|
<span className="flex items-center gap-1.5 truncate">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
|
'flex h-6 items-center gap-1 text-gray-800 dark:text-gray-100',
|
||||||
value === item ? 'font-semibold' : ''
|
value === (item?.value ?? item) ? 'font-semibold' : ''
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{item}
|
{item?.display ?? item}
|
||||||
</span>
|
</span>
|
||||||
{value === item && (
|
{value === (item?.value ?? item) && (
|
||||||
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
|
<span className="absolute inset-y-0 right-0 flex items-center pr-3 text-gray-800 dark:text-gray-100">
|
||||||
<CheckMark />
|
<CheckMark />
|
||||||
</span>
|
</span>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,37 @@
|
||||||
import React, { createContext, useRef, useContext, useCallback } from 'react';
|
import React, { createContext, useRef, useContext, useCallback } from 'react';
|
||||||
import { useScreenshot as useScreenshot_ } from 'use-react-screenshot';
|
import html2canvas from 'html2canvas';
|
||||||
|
|
||||||
const ScreenshotContext = createContext({});
|
const ScreenshotContext = createContext({});
|
||||||
|
|
||||||
export const useScreenshot = () => {
|
export const useScreenshot = () => {
|
||||||
const { ref } = useContext(ScreenshotContext);
|
const { ref } = useContext(ScreenshotContext);
|
||||||
const [image, takeScreenshot] = useScreenshot_();
|
|
||||||
|
const takeScreenShot = node => {
|
||||||
|
if (!node) {
|
||||||
|
throw new Error('You should provide correct html node.');
|
||||||
|
}
|
||||||
|
return html2canvas(node).then(canvas => {
|
||||||
|
const croppedCanvas = document.createElement('canvas');
|
||||||
|
const croppedCanvasContext = croppedCanvas.getContext('2d');
|
||||||
|
// init data
|
||||||
|
const cropPositionTop = 0;
|
||||||
|
const cropPositionLeft = 0;
|
||||||
|
const cropWidth = canvas.width;
|
||||||
|
const cropHeight = canvas.height;
|
||||||
|
|
||||||
|
croppedCanvas.width = cropWidth;
|
||||||
|
croppedCanvas.height = cropHeight;
|
||||||
|
|
||||||
|
croppedCanvasContext.drawImage(canvas, cropPositionLeft, cropPositionTop);
|
||||||
|
|
||||||
|
const base64Image = croppedCanvas.toDataURL('image/png', 1);
|
||||||
|
|
||||||
|
return base64Image;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const captureScreenshot = () => {
|
const captureScreenshot = () => {
|
||||||
return takeScreenshot(ref.current);
|
return takeScreenShot(ref.current);
|
||||||
};
|
};
|
||||||
|
|
||||||
return { screenshotTargetRef: ref, captureScreenshot };
|
return { screenshotTargetRef: ref, captureScreenshot };
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue