Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(chat): fix migration issues (Issue #668) #717

Merged
merged 16 commits into from
Feb 16, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
redesign
  • Loading branch information
Alexander-Kezik committed Feb 15, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 7f6ef03eca22281a6ab0a193b319ab9f040901d9
129 changes: 89 additions & 40 deletions apps/chat/src/components/Chat/Migration/MigrationFailedModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { IconBulb, IconCheck, IconMinus } from '@tabler/icons-react';
import {
IconBulb,
IconCheck,
IconCircleCheck,
IconDownload,
IconMinus,
} from '@tabler/icons-react';
import { ReactElement, useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';

@@ -7,17 +13,25 @@ import { useRouter } from 'next/router';

import classNames from 'classnames';

import { isMobile, isSmallScreen } from '@/src/utils/app/mobile';
import { BrowserStorage } from '@/src/utils/app/data/storages/browser-storage';
import { isSmallScreen } from '@/src/utils/app/mobile';

import { Conversation } from '@/src/types/chat';
import { Prompt } from '@/src/types/prompt';
import { MigrationStorageKeys } from '@/src/types/storage';
import { Translation } from '@/src/types/translation';

import { ConversationsActions } from '@/src/store/conversations/conversations.reducers';
import {
ConversationsActions,
ConversationsSelectors,
} from '@/src/store/conversations/conversations.reducers';
import { useAppSelector } from '@/src/store/hooks';
import { ImportExportActions } from '@/src/store/import-export/importExport.reducers';
import { ModelsSelectors } from '@/src/store/models/models.reducers';
import { PromptsActions } from '@/src/store/prompts/prompts.reducers';
import {
PromptsActions,
PromptsSelectors,
} from '@/src/store/prompts/prompts.reducers';
import { SettingsSelectors } from '@/src/store/settings/settings.reducers';

import { ReportIssueDialog } from '@/src/components/Chat/ReportIssueDialog';
@@ -154,13 +168,19 @@ export const MigrationFailedWindow = ({
string[]
>([]);
const [promptsToRetryIds, setPromptsToRetryIds] = useState<string[]>([]);

const [isReportIssueDialogOpen, setIsReportIssueDialogOpen] = useState(false);
const [dontWantBackup, setDontWantBackup] = useState(false);

const enabledFeatures = useAppSelector(
SettingsSelectors.selectEnabledFeatures,
);
const modelsMap = useAppSelector(ModelsSelectors.selectModelsMap);
const isPromptsBackedUp = useAppSelector(
PromptsSelectors.selectIsPromptsBackedUp,
);
const isChatsBackedUp = useAppSelector(
ConversationsSelectors.selectIsChatsBackedUp,
);

useEffect(() => {
setConversationsToRetryIds(
@@ -205,14 +225,15 @@ export const MigrationFailedWindow = ({
promptsToRetryIds,
]);

const onRetryWithoutBackup = useCallback(() => {
retryMigration();
}, [retryMigration]);
const onBackupPrompts = useCallback(() => {
dispatch(ImportExportActions.exportLocalStoragePrompts());
BrowserStorage.setEntityBackedUp(MigrationStorageKeys.PromptsBackedUp);
}, [dispatch]);

const onRetryWithBackup = useCallback(() => {
dispatch(ImportExportActions.exportLocalStorageEntities());
retryMigration();
}, [dispatch, retryMigration]);
const onBackupChats = useCallback(() => {
dispatch(ImportExportActions.exportLocalStorageChats());
BrowserStorage.setEntityBackedUp(MigrationStorageKeys.ChatsBackedUp);
}, [dispatch]);

const onSelectAll = useCallback(() => {
setConversationsToRetryIds(
@@ -307,44 +328,72 @@ export const MigrationFailedWindow = ({
/>
</div>
</div>
<footer className="flex items-center justify-end px-6 pt-4">
{isSmallScreen() ? (
<>
<footer className="flex flex-col items-center justify-end px-6 pt-4">
<div className="flex items-center gap-4">
<div className="relative flex size-[18px] group-hover/file-item:flex">
<input
className="checkbox peer size-[18px] bg-transparent"
type="checkbox"
onClick={() => setDontWantBackup((prev) => !prev)}
readOnly
checked={dontWantBackup}
/>
{dontWantBackup && (
<IconCheck
size={18}
className="pointer-events-none invisible absolute text-accent-primary peer-checked:visible"
/>
)}
</div>
<p className="text-secondary">
{t("I don't want to backup conversations/prompts and I’m ready.")}{' '}
<span className="font-semibold">TO LOSE DATA</span>
</p>
</div>
<div className="mt-3 flex w-full justify-end">
{!!failedMigratedPrompts.length && (
<button
className="button button-secondary mr-3 flex h-[38px] min-w-[73px] items-center"
data-qa="skip-migration"
onClick={() =>
dispatch(ImportExportActions.exportLocalStorageEntities())
}
>
{t('Backup to device')}
</button>
<button
className="button button-primary mr-3 flex h-[38px] items-center"
data-qa="skip-migration"
onClick={onRetryWithoutBackup}
onClick={onBackupPrompts}
>
{t('Continue')}
{isPromptsBackedUp ? (
<IconCircleCheck
size={18}
className="mr-3 text-accent-secondary"
/>
) : (
<IconDownload size={18} className="mr-3 text-secondary" />
)}
{!isSmallScreen() && t('Backup')} {t('prompts')}
</button>
</>
) : (
<>
)}
{!!failedMigratedConversations.length && (
<button
className="button button-secondary mr-3 flex h-[38px] min-w-[73px] items-center"
data-qa="skip-migration"
onClick={onRetryWithoutBackup}
onClick={onBackupChats}
>
{t('Continue without backup')}
{isChatsBackedUp ? (
<IconCircleCheck
size={18}
className="mr-3 text-accent-secondary"
/>
) : (
<IconDownload size={18} className="mr-3 text-secondary" />
)}
{!isSmallScreen() && t('Backup')} {t('chats')}
</button>
<button
className="button button-primary flex h-[38px] items-center"
data-qa="try-migration-again"
onClick={onRetryWithBackup}
>
{t('Backup to disk and continue')}
</button>
</>
)}
)}
<button
className="button button-primary mr-3 flex h-[38px] items-center"
data-qa="skip-migration"
onClick={retryMigration}
disabled={!dontWantBackup}
>
{t('Next')}
</button>
</div>
</footer>
</div>
<p className="mt-6 text-secondary">
56 changes: 14 additions & 42 deletions apps/chat/src/store/conversations/conversations.epics.ts
Original file line number Diff line number Diff line change
@@ -816,6 +816,9 @@ const migrateConversationsIfRequiredEpic: AppEpic = (action$, state$) => {
BrowserStorage.getFailedMigratedEntityIds(
MigrationStorageKeys.FailedMigratedConversationIds,
),
isChatsBackedUp: BrowserStorage.getEntityBackedUp(
MigrationStorageKeys.ChatsBackedUp,
),
}),
),
switchMap(
@@ -824,42 +827,8 @@ const migrateConversationsIfRequiredEpic: AppEpic = (action$, state$) => {
conversationsFolders,
migratedConversationIds,
failedMigratedConversationIds,
isChatsBackedUp,
}) => {
localStorage.setItem(
'conversationHistory',
JSON.stringify([
{
id: '45797f7c-a528-46d1-a0c8-714b5cd41f2e',
name: 'Test',
model: {
id: 'gpt-4-1106-preview',
},
prompt: '',
temperature: 1,
messages: [],
replay: {
isReplay: false,
replayUserMessagesStack: [],
activeReplayIndex: 0,
},
selectedAddons: [],
assistantModelId: 'gpt-4',
lastActivityDate: 1706876661920,
isMessageStreaming: false,
},
]),
);
localStorage.setItem(
'prompts',
JSON.stringify([
{
id: 'fc3a3e85-6f46-4afb-87fc-3af97b048ba3',
name: 'Prompt 1',
description: '',
content: 'sss',
},
]),
);
const notMigratedConversations = filterMigratedEntities(
conversations,
[...failedMigratedConversationIds, ...migratedConversationIds],
@@ -872,13 +841,16 @@ const migrateConversationsIfRequiredEpic: AppEpic = (action$, state$) => {
!notMigratedConversations.length
) {
if (failedMigratedConversationIds.length) {
return of(
ConversationsActions.setFailedMigratedConversations({
failedMigratedConversations: filterMigratedEntities(
conversations,
failedMigratedConversationIds,
),
}),
return concat(
of(ConversationsActions.setIsChatsBackedUp({ isChatsBackedUp })),
of(
ConversationsActions.setFailedMigratedConversations({
failedMigratedConversations: filterMigratedEntities(
conversations,
failedMigratedConversationIds,
),
}),
),
);
}

11 changes: 11 additions & 0 deletions apps/chat/src/store/conversations/conversations.reducers.ts
Original file line number Diff line number Diff line change
@@ -26,6 +26,7 @@ export { ConversationsSelectors };
const initialState: ConversationsState = {
conversationsToMigrateCount: 0,
migratedConversationsCount: 0,
isChatsBackedUp: false,
failedMigratedConversations: [],
conversations: [],
selectedConversationsIds: [],
@@ -80,6 +81,16 @@ export const conversationsSlice = createSlice({
) => {
state.failedMigratedConversations = payload.failedMigratedConversations;
},
setIsChatsBackedUp: (
state,
{
payload,
}: PayloadAction<{
isChatsBackedUp: boolean;
}>,
) => {
state.isChatsBackedUp = payload.isChatsBackedUp;
},
skipFailedMigratedConversations: (
state,
{ payload: _ }: PayloadAction<{ idsToMarkAsMigrated: string[] }>,
Original file line number Diff line number Diff line change
@@ -582,3 +582,8 @@ export const selectIsCompareLoading = createSelector(
return state.compareLoading;
},
);

export const selectIsChatsBackedUp = createSelector(
[rootSelector],
(state) => state.isChatsBackedUp,
);
1 change: 1 addition & 0 deletions apps/chat/src/store/conversations/conversations.types.ts
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@ import { SearchFilters } from '@/src/types/search';
export interface ConversationsState {
conversationsToMigrateCount: number;
migratedConversationsCount: number;
isChatsBackedUp: boolean;
failedMigratedConversations: Conversation[];
conversations: (ConversationInfo | Conversation)[];
selectedConversationsIds: string[];
45 changes: 30 additions & 15 deletions apps/chat/src/store/import-export/importExport.epics.ts
Original file line number Diff line number Diff line change
@@ -55,6 +55,8 @@ import { FolderType } from '@/src/types/folder';
import { LatestExportFormat } from '@/src/types/import-export';
import { AppEpic } from '@/src/types/store';

import { PromptsActions } from '@/src/store/prompts/prompts.reducers';

import { errorsMessages } from '@/src/constants/errors';

import {
@@ -178,35 +180,47 @@ const exportConversationsEpic: AppEpic = (action$, state$) =>
ignoreElements(),
);

const exportLocalStorageEntitiesEpic: AppEpic = (action$, state$) => {
const exportLocalStorageChatsEpic: AppEpic = (action$, state$) => {
const browserStorage = new BrowserStorage();

return action$.pipe(
filter(ImportExportActions.exportLocalStorageEntities.match),
filter(ImportExportActions.exportLocalStorageChats.match),
switchMap(() =>
forkJoin({
conversations: browserStorage
.getConversations()
.pipe(map(filterOnlyMyEntities)),
conversationFolders: browserStorage.getConversationsFolders(),
appName: SettingsSelectors.selectAppName(state$.value),
}),
),
tap(({ conversations, conversationFolders, appName }) => {
exportConversations(conversations, conversationFolders, appName, 4);
}),
switchMap(() =>
of(ConversationsActions.setIsChatsBackedUp({ isChatsBackedUp: true })),
),
);
};

const exportLocalStoragePromptsEpic: AppEpic = (action$, state$) => {
const browserStorage = new BrowserStorage();

return action$.pipe(
filter(ImportExportActions.exportLocalStoragePrompts.match),
switchMap(() =>
forkJoin({
prompts: browserStorage.getPrompts().pipe(map(filterOnlyMyEntities)),
promptFolders: browserStorage.getPromptsFolders(),
appName: SettingsSelectors.selectAppName(state$.value),
}),
),
tap(
({
conversations,
conversationFolders,
prompts,
promptFolders,
appName,
}) => {
exportConversations(conversations, conversationFolders, appName, 4);
exportPrompts(prompts, promptFolders);
},
tap(({ prompts, promptFolders, appName }) => {
exportPrompts(prompts, promptFolders, appName);
}),
switchMap(() =>
of(PromptsActions.setIsPromptsBackedUp({ isPromptsBackedUp: true })),
),
ignoreElements(),
);
};

@@ -654,5 +668,6 @@ export const ImportExportEpics = combineEpics(
importFailEpic,
exportFailEpic,
checkImportFailEpic,
exportLocalStorageEntitiesEpic,
exportLocalStorageChatsEpic,
exportLocalStoragePromptsEpic,
);
Loading
Loading