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