mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-08-28 12:44:28 +00:00
📁 fix: Consistent Export Filenames Across Formats (#14708)
* 📁 fix: Consistent Export Filenames Across Formats Conversation exports produced different filenames per format: txt/md/csv go through export-from-json, whose default formatter replaces only the first whitespace run (non-global regex), while png/json downloads keep spaces. Normalize the filename once before export so every format yields Word1_Word2_Word3.ext; the preset JSON export shared the same defect. * 🧹 chore: fix import order via sort-imports
This commit is contained in:
parent
87a8b9aa12
commit
26bcbb713c
4 changed files with 45 additions and 4 deletions
|
|
@ -11,6 +11,7 @@ import {
|
|||
} from '@librechat/client';
|
||||
import type { TConversation } from 'librechat-data-provider';
|
||||
import { useLocalize, useExportConversation } from '~/hooks';
|
||||
import { normalizeExportFilename } from '~/utils';
|
||||
|
||||
const TYPE_OPTIONS = [
|
||||
{ value: 'screenshot', label: 'screenshot (.png)' },
|
||||
|
|
@ -72,7 +73,7 @@ export default function ExportModal({
|
|||
|
||||
const { exportConversation } = useExportConversation({
|
||||
conversation,
|
||||
filename: filenamify(filename),
|
||||
filename: normalizeExportFilename(filenamify(filename)),
|
||||
type,
|
||||
includeOptions,
|
||||
exportBranches,
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import filenamify from 'filenamify';
|
||||
import exportFromJSON from 'export-from-json';
|
||||
import { useToastContext } from '@librechat/client';
|
||||
import { QueryKeys } from 'librechat-data-provider';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useRecoilState, useSetRecoilState, useRecoilValue } from 'recoil';
|
||||
import { useCreatePresetMutation, useGetModelsQuery } from 'librechat-data-provider/react-query';
|
||||
import type { TPreset, TEndpointsConfig } from 'librechat-data-provider';
|
||||
import {
|
||||
normalizeExportFilename,
|
||||
removeUnavailableTools,
|
||||
getConvoSwitchLogic,
|
||||
cleanupPreset,
|
||||
} from '~/utils';
|
||||
import {
|
||||
useUpdatePresetMutation,
|
||||
useDeletePresetMutation,
|
||||
useGetPresetsQuery,
|
||||
} from '~/data-provider';
|
||||
import { cleanupPreset, removeUnavailableTools, getConvoSwitchLogic } from '~/utils';
|
||||
import useGetConversation from '~/hooks/Conversations/useGetConversation';
|
||||
import useDefaultConvo from '~/hooks/Conversations/useDefaultConvo';
|
||||
import { useAuthContext } from '~/hooks/AuthContext';
|
||||
|
|
@ -264,7 +269,7 @@ export default function usePresets(index = 0) {
|
|||
if (!preset) {
|
||||
return;
|
||||
}
|
||||
const fileName = filenamify(preset.title || 'preset');
|
||||
const fileName = normalizeExportFilename(filenamify(preset.title || 'preset'));
|
||||
exportFromJSON({
|
||||
data: cleanupPreset({ preset }),
|
||||
fileName,
|
||||
|
|
|
|||
26
client/src/utils/files.spec.ts
Normal file
26
client/src/utils/files.spec.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { normalizeExportFilename } from './files';
|
||||
|
||||
describe('normalizeExportFilename', () => {
|
||||
it('replaces every whitespace run with a single underscore', () => {
|
||||
expect(normalizeExportFilename('Word1 Word2 Word3')).toBe('Word1_Word2_Word3');
|
||||
});
|
||||
|
||||
it('collapses consecutive whitespace into one underscore', () => {
|
||||
expect(normalizeExportFilename('Word1 Word2\tWord3')).toBe('Word1_Word2_Word3');
|
||||
});
|
||||
|
||||
it('leaves filenames without whitespace unchanged', () => {
|
||||
expect(normalizeExportFilename('single-word_name')).toBe('single-word_name');
|
||||
});
|
||||
|
||||
it('handles an empty string', () => {
|
||||
expect(normalizeExportFilename('')).toBe('');
|
||||
});
|
||||
|
||||
it('never leaves whitespace, so downstream whitespace-based formatters are no-ops', () => {
|
||||
const inputs = ['Word1 Word2 Word3', ' leading', 'trailing ', 'tab\there', 'a b c'];
|
||||
for (const input of inputs) {
|
||||
expect(normalizeExportFilename(input)).not.toMatch(/\s/);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -439,3 +439,12 @@ export function sortPagesByRelevance(
|
|||
}
|
||||
return [...pages].sort((a, b) => (pageRelevance[b] || 0) - (pageRelevance[a] || 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses whitespace runs to underscores so export filenames come out
|
||||
* identical across all export formats: `export-from-json`'s default formatter
|
||||
* only replaces the first whitespace run, while direct downloads replace none.
|
||||
*/
|
||||
export function normalizeExportFilename(filename: string): string {
|
||||
return filename.replace(/\s+/g, '_');
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue