2025-10-07 20:12:49 +02:00
|
|
|
import { useState, useRef, useCallback } from 'react';
|
2024-05-02 08:48:26 +02:00
|
|
|
import { Import } from 'lucide-react';
|
2025-10-07 14:47:21 -04:00
|
|
|
import { useQueryClient } from '@tanstack/react-query';
|
|
|
|
|
import { QueryKeys, TStartupConfig } from 'librechat-data-provider';
|
2025-10-07 20:12:49 +02:00
|
|
|
import { Spinner, useToastContext, Label, Button } from '@librechat/client';
|
2024-05-02 08:48:26 +02:00
|
|
|
import { useUploadConversationsMutation } from '~/data-provider';
|
2025-10-07 20:12:49 +02:00
|
|
|
import { NotificationSeverity } from '~/common';
|
2025-01-12 12:57:10 -05:00
|
|
|
import { useLocalize } from '~/hooks';
|
2025-10-07 20:12:49 +02:00
|
|
|
import { cn, logger } from '~/utils';
|
2024-05-02 08:48:26 +02:00
|
|
|
|
|
|
|
|
function ImportConversations() {
|
|
|
|
|
const localize = useLocalize();
|
2025-10-28 09:36:03 -04:00
|
|
|
const queryClient = useQueryClient();
|
2024-05-02 08:48:26 +02:00
|
|
|
const { showToast } = useToastContext();
|
2025-10-28 09:36:03 -04:00
|
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
2025-10-07 20:12:49 +02:00
|
|
|
const [isUploading, setIsUploading] = useState(false);
|
|
|
|
|
|
|
|
|
|
const handleSuccess = useCallback(() => {
|
|
|
|
|
showToast({
|
|
|
|
|
message: localize('com_ui_import_conversation_success'),
|
|
|
|
|
status: NotificationSeverity.SUCCESS,
|
|
|
|
|
});
|
|
|
|
|
setIsUploading(false);
|
|
|
|
|
}, [localize, showToast]);
|
|
|
|
|
|
|
|
|
|
const handleError = useCallback(
|
|
|
|
|
(error: unknown) => {
|
|
|
|
|
logger.error('Import error:', error);
|
|
|
|
|
setIsUploading(false);
|
|
|
|
|
|
|
|
|
|
const isUnsupportedType = error?.toString().includes('Unsupported import type');
|
|
|
|
|
|
|
|
|
|
showToast({
|
|
|
|
|
message: localize(
|
|
|
|
|
isUnsupportedType
|
|
|
|
|
? 'com_ui_import_conversation_file_type_error'
|
|
|
|
|
: 'com_ui_import_conversation_error',
|
|
|
|
|
),
|
|
|
|
|
status: NotificationSeverity.ERROR,
|
|
|
|
|
});
|
2024-05-02 08:48:26 +02:00
|
|
|
},
|
2025-10-07 20:12:49 +02:00
|
|
|
[localize, showToast],
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const uploadFile = useUploadConversationsMutation({
|
|
|
|
|
onSuccess: handleSuccess,
|
|
|
|
|
onError: handleError,
|
|
|
|
|
onMutate: () => setIsUploading(true),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const handleFileUpload = useCallback(
|
|
|
|
|
async (file: File) => {
|
|
|
|
|
try {
|
2025-10-28 09:36:03 -04:00
|
|
|
const startupConfig = queryClient.getQueryData<TStartupConfig>([QueryKeys.startupConfig]);
|
|
|
|
|
const maxFileSize = startupConfig?.conversationImportMaxFileSize;
|
2025-10-07 14:47:21 -04:00
|
|
|
if (maxFileSize && file.size > maxFileSize) {
|
|
|
|
|
const size = (maxFileSize / (1024 * 1024)).toFixed(2);
|
|
|
|
|
showToast({
|
|
|
|
|
message: localize('com_error_files_upload_too_large', { 0: size }),
|
|
|
|
|
status: NotificationSeverity.ERROR,
|
|
|
|
|
});
|
|
|
|
|
setIsUploading(false);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-07 20:12:49 +02:00
|
|
|
const formData = new FormData();
|
|
|
|
|
formData.append('file', file, encodeURIComponent(file.name || 'File'));
|
|
|
|
|
uploadFile.mutate(formData);
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.error('File processing error:', error);
|
|
|
|
|
setIsUploading(false);
|
2024-05-02 08:48:26 +02:00
|
|
|
showToast({
|
2025-10-07 20:12:49 +02:00
|
|
|
message: localize('com_ui_import_conversation_upload_error'),
|
|
|
|
|
status: NotificationSeverity.ERROR,
|
2024-05-02 08:48:26 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-10-28 09:36:03 -04:00
|
|
|
[uploadFile, showToast, localize, queryClient],
|
2025-10-07 20:12:49 +02:00
|
|
|
);
|
2024-05-02 08:48:26 +02:00
|
|
|
|
2025-10-07 20:12:49 +02:00
|
|
|
const handleFileChange = useCallback(
|
|
|
|
|
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const file = event.target.files?.[0];
|
|
|
|
|
if (file) {
|
2025-10-07 14:47:21 -04:00
|
|
|
setIsUploading(true);
|
2025-10-07 20:12:49 +02:00
|
|
|
handleFileUpload(file);
|
|
|
|
|
}
|
|
|
|
|
event.target.value = '';
|
|
|
|
|
},
|
|
|
|
|
[handleFileUpload],
|
|
|
|
|
);
|
2024-05-02 08:48:26 +02:00
|
|
|
|
2025-10-07 20:12:49 +02:00
|
|
|
const handleImportClick = useCallback(() => {
|
2024-09-10 10:11:39 -09:00
|
|
|
fileInputRef.current?.click();
|
2025-10-07 20:12:49 +02:00
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleKeyDown = useCallback(
|
|
|
|
|
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
|
|
|
if (event.key === 'Enter' || event.key === ' ') {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
handleImportClick();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[handleImportClick],
|
|
|
|
|
);
|
2024-09-10 10:11:39 -09:00
|
|
|
|
2025-10-07 20:12:49 +02:00
|
|
|
const isImportDisabled = isUploading;
|
2024-09-10 10:11:39 -09:00
|
|
|
|
2024-05-02 08:48:26 +02:00
|
|
|
return (
|
|
|
|
|
<div className="flex items-center justify-between">
|
2025-10-07 20:12:49 +02:00
|
|
|
<Label id="import-conversation-label">{localize('com_ui_import_conversation_info')}</Label>
|
|
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
2024-09-10 10:11:39 -09:00
|
|
|
onClick={handleImportClick}
|
|
|
|
|
onKeyDown={handleKeyDown}
|
2025-10-07 20:12:49 +02:00
|
|
|
disabled={isImportDisabled}
|
2025-02-18 01:09:36 +01:00
|
|
|
aria-label={localize('com_ui_import')}
|
2025-10-07 20:12:49 +02:00
|
|
|
aria-labelledby="import-conversation-label"
|
2024-09-10 10:11:39 -09:00
|
|
|
>
|
2025-10-07 20:12:49 +02:00
|
|
|
{isUploading ? (
|
🌿 feat: Fork Messages/Conversations (#2617)
* typedef for ImportBatchBuilder
* feat: first pass, fork conversations
* feat: fork - getMessagesUpToTargetLevel
* fix: additional tests and fix getAllMessagesUpToParent
* chore: arrow function return
* refactor: fork 3 options
* chore: remove unused genbuttons
* chore: remove unused hover buttons code
* feat: fork first pass
* wip: fork remember setting
* style: user icon
* chore: move clear chats to data tab
* WIP: fork UI options
* feat: data-provider fork types/services/vars and use generic MutationOptions
* refactor: use single param for fork option, use enum, fix mongo errors, use Date.now(), add records flag for testing, use endpoint from original convo and messages, pass originalConvo to finishConversation
* feat: add fork mutation hook and consolidate type imports
* refactor: use enum
* feat: first pass, fork mutation
* chore: add enum for target level fork option
* chore: add enum for target level fork option
* show toast when checking remember selection
* feat: splitAtTarget
* feat: split at target option
* feat: navigate to new fork, show toasts, set result query data
* feat: hover info for all fork options
* refactor: add Messages settings tab
* fix(Fork): remember text info
* ci: test for single message and is target edge case
* feat: additional tests for getAllMessagesUpToParent
* ci: additional tests and cycle detection for getMessagesUpToTargetLevel
* feat: circular dependency checks for getAllMessagesUpToParent
* fix: getMessagesUpToTargetLevel circular dep. check
* ci: more tests for getMessagesForConversation
* style: hover text for checkbox fork items
* refactor: add statefulness to conversation import
2024-05-05 11:48:20 -04:00
|
|
|
<Spinner className="mr-1 w-4" />
|
2025-10-07 20:12:49 +02:00
|
|
|
) : (
|
2025-12-11 15:35:17 -06:00
|
|
|
<Import className="mr-1 flex h-4 w-4 items-center stroke-1" aria-hidden="true" />
|
🌿 feat: Fork Messages/Conversations (#2617)
* typedef for ImportBatchBuilder
* feat: first pass, fork conversations
* feat: fork - getMessagesUpToTargetLevel
* fix: additional tests and fix getAllMessagesUpToParent
* chore: arrow function return
* refactor: fork 3 options
* chore: remove unused genbuttons
* chore: remove unused hover buttons code
* feat: fork first pass
* wip: fork remember setting
* style: user icon
* chore: move clear chats to data tab
* WIP: fork UI options
* feat: data-provider fork types/services/vars and use generic MutationOptions
* refactor: use single param for fork option, use enum, fix mongo errors, use Date.now(), add records flag for testing, use endpoint from original convo and messages, pass originalConvo to finishConversation
* feat: add fork mutation hook and consolidate type imports
* refactor: use enum
* feat: first pass, fork mutation
* chore: add enum for target level fork option
* chore: add enum for target level fork option
* show toast when checking remember selection
* feat: splitAtTarget
* feat: split at target option
* feat: navigate to new fork, show toasts, set result query data
* feat: hover info for all fork options
* refactor: add Messages settings tab
* fix(Fork): remember text info
* ci: test for single message and is target edge case
* feat: additional tests for getAllMessagesUpToParent
* ci: additional tests and cycle detection for getMessagesUpToTargetLevel
* feat: circular dependency checks for getAllMessagesUpToParent
* fix: getMessagesUpToTargetLevel circular dep. check
* ci: more tests for getMessagesForConversation
* style: hover text for checkbox fork items
* refactor: add statefulness to conversation import
2024-05-05 11:48:20 -04:00
|
|
|
)}
|
2025-02-18 01:09:36 +01:00
|
|
|
<span>{localize('com_ui_import')}</span>
|
2025-10-07 20:12:49 +02:00
|
|
|
</Button>
|
2024-09-10 10:11:39 -09:00
|
|
|
<input
|
|
|
|
|
ref={fileInputRef}
|
|
|
|
|
type="file"
|
|
|
|
|
className={cn('hidden')}
|
|
|
|
|
accept=".json"
|
|
|
|
|
onChange={handleFileChange}
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
/>
|
2024-05-02 08:48:26 +02:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default ImportConversations;
|