helloya20's picture
Upload 2345 files
f0743f4 verified
import filenamify from 'filenamify';
import { useEffect, useState, useMemo, useCallback } from 'react';
import {
OGDialogTemplate,
OGDialog,
Button,
Input,
Label,
Checkbox,
Dropdown,
} from '@librechat/client';
import type { TConversation } from 'librechat-data-provider';
import { useLocalize, useExportConversation } from '~/hooks';
const TYPE_OPTIONS = [
{ value: 'screenshot', label: 'screenshot (.png)' },
{ value: 'text', label: 'text (.txt)' },
{ value: 'markdown', label: 'markdown (.md)' },
{ value: 'json', label: 'json (.json)' },
{ value: 'csv', label: 'csv (.csv)' },
];
export default function ExportModal({
open,
onOpenChange,
conversation,
triggerRef,
children,
}: {
open: boolean;
conversation: TConversation | null;
onOpenChange: React.Dispatch<React.SetStateAction<boolean>>;
triggerRef?: React.RefObject<HTMLButtonElement>;
children?: React.ReactNode;
}) {
const localize = useLocalize();
const [filename, setFileName] = useState('');
const [type, setType] = useState<string>('screenshot');
const [includeOptions, setIncludeOptions] = useState<boolean | 'indeterminate'>(true);
const [exportBranches, setExportBranches] = useState<boolean | 'indeterminate'>(false);
const [recursive, setRecursive] = useState<boolean | 'indeterminate'>(true);
useEffect(() => {
if (!open && triggerRef && triggerRef.current) {
triggerRef.current.focus();
}
}, [open, triggerRef]);
useEffect(() => {
setFileName(filenamify(String(conversation?.title ?? 'file')));
setType('screenshot');
setIncludeOptions(true);
setExportBranches(false);
setRecursive(true);
}, [conversation?.title, open]);
const handleTypeChange = useCallback((newType: string) => {
const branches = newType === 'json' || newType === 'csv' || newType === 'webpage';
const options = newType !== 'csv' && newType !== 'screenshot';
setExportBranches(branches);
setIncludeOptions(options);
setType(newType);
}, []);
const exportBranchesSupport = useMemo(
() => type === 'json' || type === 'csv' || type === 'webpage',
[type],
);
const exportOptionsSupport = useMemo(() => type !== 'csv' && type !== 'screenshot', [type]);
const { exportConversation } = useExportConversation({
conversation,
filename: filenamify(filename),
type,
includeOptions,
exportBranches,
recursive,
});
return (
<OGDialog open={open} onOpenChange={onOpenChange} triggerRef={triggerRef}>
{children}
<OGDialogTemplate
title={localize('com_nav_export_conversation')}
className="max-w-full sm:max-w-2xl"
main={
<div className="flex w-full flex-col items-center gap-6">
<div className="grid w-full gap-6 sm:grid-cols-2">
<div className="col-span-1 flex flex-col items-start justify-start gap-2">
<Label htmlFor="filename" className="text-left text-sm font-medium">
{localize('com_nav_export_filename')}
</Label>
<Input
id="filename"
value={filename}
onChange={(e) => setFileName(e.target.value || '')}
placeholder={localize('com_nav_export_filename_placeholder')}
/>
</div>
<div className="col-span-1 flex w-full flex-col items-start justify-start gap-2">
<Label htmlFor="type" className="text-left text-sm font-medium">
{localize('com_nav_export_type')}
</Label>
<Dropdown
value={type}
onChange={handleTypeChange}
options={TYPE_OPTIONS}
className="z-50"
portal={false}
/>
</div>
</div>
<div className="grid w-full gap-6 sm:grid-cols-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">
{localize('com_nav_export_include_endpoint_options')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="includeOptions"
disabled={!exportOptionsSupport}
checked={includeOptions}
onCheckedChange={setIncludeOptions}
aria-labelledby="includeOptions-label"
/>
<label
id="includeOptions-label"
htmlFor="includeOptions"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{exportOptionsSupport
? localize('com_nav_export_include_endpoint_options')
: localize('com_nav_not_supported')}
</label>
</div>
</div>
</div>
<div className="grid w-full items-center gap-2">
<Label htmlFor="exportBranches" className="text-left text-sm font-medium">
{localize('com_nav_export_all_message_branches')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="exportBranches"
disabled={!exportBranchesSupport}
checked={exportBranches}
onCheckedChange={setExportBranches}
aria-labelledby="exportBranches-label"
/>
<label
id="exportBranches-label"
htmlFor="exportBranches"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{exportBranchesSupport
? localize('com_nav_export_all_message_branches')
: localize('com_nav_not_supported')}
</label>
</div>
</div>
{type === 'json' ? (
<div className="grid w-full items-center gap-2">
<Label htmlFor="recursive" className="text-left text-sm font-medium">
{localize('com_nav_export_recursive_or_sequential')}
</Label>
<div className="flex h-[40px] w-full items-center space-x-3">
<Checkbox
id="recursive"
checked={recursive}
onCheckedChange={setRecursive}
aria-labelledby="recursive-label"
/>
<label
id="recursive-label"
htmlFor="recursive"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 dark:text-gray-50"
>
{localize('com_nav_export_recursive')}
</label>
</div>
</div>
) : null}
</div>
</div>
}
buttons={
<>
<Button onClick={exportConversation} variant="submit">
{localize('com_endpoint_export')}
</Button>
</>
}
selection={undefined}
/>
</OGDialog>
);
}