| 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> | |
| ); | |
| } | |