diff --git a/apps/chat/src/components/Chat/Migration/Migration.tsx b/apps/chat/src/components/Chat/Migration/Migration.tsx index 345858fd90..05fe31a009 100644 --- a/apps/chat/src/components/Chat/Migration/Migration.tsx +++ b/apps/chat/src/components/Chat/Migration/Migration.tsx @@ -17,11 +17,12 @@ export const Migration = ({ total, uploaded }: Props) => { return (
-

+

+ {t('Migration')} +

+

{uploaded} {t('out of')} {total}
- - {t('conversations and prompts are loaded')} - + {t('conversations and prompts are loaded')}

diff --git a/apps/chat/src/components/Chat/Migration/MigrationFailedModal.tsx b/apps/chat/src/components/Chat/Migration/MigrationFailedModal.tsx index f8097e1bd5..845ab4777d 100644 --- a/apps/chat/src/components/Chat/Migration/MigrationFailedModal.tsx +++ b/apps/chat/src/components/Chat/Migration/MigrationFailedModal.tsx @@ -46,19 +46,21 @@ const ItemsList = ({ setEntitiesToRetryIds(entitiesToRetryIds.filter((id) => id !== entityId)); }; - return failedMigratedEntities.length ? ( + if (!failedMigratedEntities.length) return null; + + return (

    {failedMigratedEntities.map((entity) => (
  • -
    +
    {getModelIcon(entity)} -

    {entity.name}

    +

    {entity.name}

    -
    +
    handleSelect(entity.id)} className="relative flex size-[18px] group-hover/file-item:flex" @@ -94,7 +96,7 @@ const ItemsList = ({ ))}
- ) : null; + ); }; interface Props { @@ -102,6 +104,40 @@ interface Props { failedMigratedPrompts: Prompt[]; } +interface AllItemsCheckboxesProps { + isChecked: boolean; + isCheckIcon: boolean; + isMinusIcon: boolean; + onSelectHandler: () => void; +} + +const AllItemsCheckboxes = ({ + isChecked, + isCheckIcon, + isMinusIcon, + onSelectHandler, +}: AllItemsCheckboxesProps) => { + const Icon = isCheckIcon ? IconCheck : isMinusIcon ? IconMinus : null; + + return ( +
+ + {Icon && ( + + )} +
+ ); +}; + export const MigrationFailedWindow = ({ failedMigratedConversations, failedMigratedPrompts, @@ -134,20 +170,7 @@ export const MigrationFailedWindow = ({ setPromptsToRetryIds(failedMigratedPrompts.map((prompt) => prompt.id)); }, [failedMigratedPrompts]); - const onSkipAll = useCallback(() => { - dispatch( - ConversationsActions.skipFailedMigratedConversations({ - idsToMarkAsMigrated: failedMigratedConversations.map((conv) => conv.id), - }), - ); - dispatch( - PromptsActions.skipFailedMigratedPrompts({ - idsToMarkAsMigrated: failedMigratedPrompts.map((prompt) => prompt.id), - }), - ); - }, [dispatch, failedMigratedConversations, failedMigratedPrompts]); - - const onRetry = useCallback(() => { + const retryMigration = useCallback(() => { const failedMigratedConversationIds = failedMigratedConversations.map( (conv) => conv.id, ); @@ -155,7 +178,6 @@ export const MigrationFailedWindow = ({ (conv) => conv.id, ); - dispatch(ImportExportActions.exportLocalStorageEntities()); dispatch( ConversationsActions.skipFailedMigratedConversations({ idsToMarkAsMigrated: failedMigratedConversationIds.filter( @@ -170,8 +192,9 @@ export const MigrationFailedWindow = ({ ), }), ); - dispatch(ConversationsActions.migrateConversations()); - dispatch(PromptsActions.migratePrompts()); + + dispatch(ConversationsActions.migrateConversationsIfRequired()); + dispatch(PromptsActions.migratePromptsIfRequired()); }, [ conversationsToRetryIds, dispatch, @@ -180,6 +203,15 @@ export const MigrationFailedWindow = ({ promptsToRetryIds, ]); + const onRetryWithoutBackup = useCallback(() => { + retryMigration(); + }, [retryMigration]); + + const onRetryWithBackup = useCallback(() => { + dispatch(ImportExportActions.exportLocalStorageEntities()); + retryMigration(); + }, [dispatch, retryMigration]); + const onSelectAll = useCallback(() => { setConversationsToRetryIds( failedMigratedConversations.map((conv) => conv.id), @@ -192,6 +224,15 @@ export const MigrationFailedWindow = ({ setPromptsToRetryIds([]); }; + const isAllItemsSelected = + conversationsToRetryIds.length + promptsToRetryIds.length === + failedMigratedPrompts.length + failedMigratedConversations.length; + const isSomeItemsSelected = + !!conversationsToRetryIds.length || !!promptsToRetryIds.length; + const isNothingSelected = + conversationsToRetryIds.length === 0 && + conversationsToRetryIds.length === 0; + return (
@@ -220,40 +261,19 @@ export const MigrationFailedWindow = ({
{t('All items')}
-
-
- - -
-
- - -
+
+ +
@@ -290,14 +310,14 @@ export const MigrationFailedWindow = ({ diff --git a/apps/chat/src/store/conversations/conversations.epics.ts b/apps/chat/src/store/conversations/conversations.epics.ts index c95b7b11d0..d8f8379980 100644 --- a/apps/chat/src/store/conversations/conversations.epics.ts +++ b/apps/chat/src/store/conversations/conversations.epics.ts @@ -705,18 +705,18 @@ const deleteConversationsEpic: AppEpic = (action$, state$) => }), ); -const migrateConversationsEpic: AppEpic = (action$, state$) => { +const migrateConversationsIfRequiredEpic: AppEpic = (action$, state$) => { const browserStorage = new BrowserStorage(); return action$.pipe( - filter(ConversationsActions.migrateConversations.match), + filter(ConversationsActions.migrateConversationsIfRequired.match), switchMap(() => forkJoin({ conversations: browserStorage .getConversations() .pipe(map(filterOnlyMyEntities)), conversationsFolders: browserStorage - .getConversationsFolders() + .getConversationsFolders(undefined, true) .pipe(map(filterOnlyMyEntities)), migratedConversationIds: BrowserStorage.getMigratedEntityIds( MigrationStorageKeys.MigratedConversationIds, @@ -769,6 +769,7 @@ const migrateConversationsEpic: AppEpic = (action$, state$) => { const { path } = getPathToFolderById( conversationsFolders, conv.folderId, + true, ); const newName = conv.name.replace(notAllowedSymbolsRegex, ''); @@ -776,7 +777,7 @@ const migrateConversationsEpic: AppEpic = (action$, state$) => { ...conv, id: constructPath(...[path, newName]), name: newName, - folderId: path.replace(notAllowedSymbolsRegex, ''), + folderId: path, }; }); // to send conversation with proper parentPath and lastActivityDate order @@ -2238,7 +2239,7 @@ const openFolderEpic: AppEpic = (action$, state$) => ); export const ConversationsEpics = combineEpics( - migrateConversationsEpic, + migrateConversationsIfRequiredEpic, skipFailedMigratedConversationsEpic, // init initEpic, diff --git a/apps/chat/src/store/conversations/conversations.reducers.ts b/apps/chat/src/store/conversations/conversations.reducers.ts index d6cba5062d..8bd33b1a56 100644 --- a/apps/chat/src/store/conversations/conversations.reducers.ts +++ b/apps/chat/src/store/conversations/conversations.reducers.ts @@ -55,7 +55,7 @@ export const conversationsSlice = createSlice({ initialState, reducers: { init: (state) => state, - migrateConversations: (state) => state, + migrateConversationsIfRequired: (state) => state, initConversationsMigration: ( state, { diff --git a/apps/chat/src/store/prompts/prompts.epics.ts b/apps/chat/src/store/prompts/prompts.epics.ts index 69382ed097..8a246cc69d 100644 --- a/apps/chat/src/store/prompts/prompts.epics.ts +++ b/apps/chat/src/store/prompts/prompts.epics.ts @@ -402,16 +402,16 @@ const importPromptsEpic: AppEpic = (action$, state$) => // ), // ); -const migratePromptsEpic: AppEpic = (action$, state$) => { +const migratePromptsIfRequiredEpic: AppEpic = (action$, state$) => { const browserStorage = new BrowserStorage(); return action$.pipe( - filter(PromptsActions.migratePrompts.match), + filter(PromptsActions.migratePromptsIfRequired.match), switchMap(() => forkJoin({ prompts: browserStorage.getPrompts().pipe(map(filterOnlyMyEntities)), promptsFolders: browserStorage - .getPromptsFolders() + .getPromptsFolders(undefined, true) .pipe(map(filterOnlyMyEntities)), migratedPromptIds: BrowserStorage.getMigratedEntityIds( MigrationStorageKeys.MigratedPromptIds, @@ -453,14 +453,18 @@ const migratePromptsEpic: AppEpic = (action$, state$) => { } const preparedPrompts: Prompt[] = notMigratedPrompts.map((prompt) => { - const { path } = getPathToFolderById(promptsFolders, prompt.folderId); + const { path } = getPathToFolderById( + promptsFolders, + prompt.folderId, + true, + ); const newName = prompt.name.replace(notAllowedSymbolsRegex, ''); return { ...prompt, id: constructPath(...[path, newName]), name: newName, - folderId: path.replace(notAllowedSymbolsRegex, ''), + folderId: path, }; }); // to send prompts with proper parentPath let migratedPromptsCount = 0; @@ -834,7 +838,7 @@ export const uploadPromptEpic: AppEpic = (action$, state$) => ); export const PromptsEpics = combineEpics( - migratePromptsEpic, + migratePromptsIfRequiredEpic, skipFailedMigratedPromptsEpic, // init initEpic, diff --git a/apps/chat/src/store/prompts/prompts.reducers.ts b/apps/chat/src/store/prompts/prompts.reducers.ts index 181acddbeb..e59c672e48 100644 --- a/apps/chat/src/store/prompts/prompts.reducers.ts +++ b/apps/chat/src/store/prompts/prompts.reducers.ts @@ -46,7 +46,7 @@ export const promptsSlice = createSlice({ reducers: { init: (state) => state, initPrompts: (state) => state, - migratePrompts: (state) => state, + migratePromptsIfRequired: (state) => state, skipFailedMigratedPrompts: ( state, { payload: _ }: PayloadAction<{ idsToMarkAsMigrated: string[] }>, diff --git a/apps/chat/src/store/settings/settings.epic.ts b/apps/chat/src/store/settings/settings.epic.ts index d0be1c2670..6d75d236cb 100644 --- a/apps/chat/src/store/settings/settings.epic.ts +++ b/apps/chat/src/store/settings/settings.epic.ts @@ -63,8 +63,8 @@ const initEpic: AppEpic = (action$, state$) => ), switchMap(() => concat( - of(ConversationsActions.migrateConversations()), - of(PromptsActions.migratePrompts()), + of(ConversationsActions.migrateConversationsIfRequired()), + of(PromptsActions.migratePromptsIfRequired()), of(UIActions.init()), of(ModelsActions.init()), of(AddonsActions.init()), diff --git a/apps/chat/src/utils/app/data/storages/browser-storage.ts b/apps/chat/src/utils/app/data/storages/browser-storage.ts index 7539f6b3d9..1d1828dcbe 100644 --- a/apps/chat/src/utils/app/data/storages/browser-storage.ts +++ b/apps/chat/src/utils/app/data/storages/browser-storage.ts @@ -172,23 +172,25 @@ export class BrowserStorage implements DialStorage { return BrowserStorage.setData(UIStorageKeys.Prompts, prompts); } - getConversationsFolders(path?: string) { + getConversationsFolders(path?: string, recursive?: boolean) { return BrowserStorage.getData(UIStorageKeys.Folders, []).pipe( map((folders: FolderInterface[]) => { return folders.filter( (folder) => - folder.type === FolderType.Chat && folder.folderId === path, + folder.type === FolderType.Chat && + (recursive || folder.folderId === path), ); }), ); } - getPromptsFolders(path?: string) { + getPromptsFolders(path?: string, recursive?: boolean) { return BrowserStorage.getData(UIStorageKeys.Folders, []).pipe( map((folders: FolderInterface[]) => { return folders.filter( (folder) => - folder.type === FolderType.Prompt && folder.folderId === path, + folder.type === FolderType.Prompt && + (recursive || folder.folderId === path), ); }), ); diff --git a/apps/chat/src/utils/app/folders.ts b/apps/chat/src/utils/app/folders.ts index 9e667bcf43..d21b64fad0 100644 --- a/apps/chat/src/utils/app/folders.ts +++ b/apps/chat/src/utils/app/folders.ts @@ -12,6 +12,8 @@ import { FolderInterface, FolderType } from '@/src/types/folder'; import { Prompt, PromptInfo } from '@/src/types/prompt'; import { EntityFilters } from '@/src/types/search'; +import { DEFAULT_FOLDER_NAME } from '@/src/constants/default-settings'; + import escapeStringRegexp from 'escape-string-regexp'; export const getFoldersDepth = ( @@ -193,13 +195,18 @@ export const getFolderIdByPath = (path: string, folders: FolderInterface[]) => { export const getPathToFolderById = ( folders: FolderInterface[], starterId: string | undefined, + removeNotAllowedSymbols = false, ) => { const path: string[] = []; const createPath = (folderId: string) => { const folder = folders.find((folder) => folder.id === folderId); if (!folder) return; - path.unshift(folder.name); + path.unshift( + removeNotAllowedSymbols + ? folder.name.replace(notAllowedSymbolsRegex, '') || DEFAULT_FOLDER_NAME + : folder.name, + ); if (folder.folderId) { createPath(folder.folderId);