fix import (#242)
* 🐛 fix import (#224) * 🐛 fix import of corrupted history see https://github.com/mckaywrigley/chatbot-ui/issues/224#issuecomment-1486080888 * add the run-test-suite github action
This commit is contained in:
parent
5aa5be3f43
commit
b0c289f7a4
|
@ -1,4 +1,4 @@
|
||||||
.env
|
.env
|
||||||
.env.local
|
.env.local
|
||||||
node_modules
|
node_modules
|
||||||
|
test-results
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
name: Run Jest Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: node:16
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Run Jest Test Suite
|
||||||
|
run: npm test
|
||||||
|
|
||||||
|
- name: Publish Test Report
|
||||||
|
if: always()
|
||||||
|
uses: EnricoMi/publish-unit-test-result-action@v1
|
||||||
|
with:
|
||||||
|
files: test-results/**/results.xml
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
/test-results
|
||||||
|
|
||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import { ExportFormatV1, ExportFormatV2 } from '@/types/export';
|
||||||
|
import { OpenAIModels, OpenAIModelID } from '@/types/openai';
|
||||||
|
import { DEFAULT_SYSTEM_PROMPT } from '@/utils/app/const';
|
||||||
|
import {
|
||||||
|
cleanData,
|
||||||
|
isExportFormatV1,
|
||||||
|
isExportFormatV2,
|
||||||
|
isExportFormatV3,
|
||||||
|
isLatestExportFormat,
|
||||||
|
} from '@/utils/app/importExport';
|
||||||
|
|
||||||
|
describe('Export Format Functions', () => {
|
||||||
|
describe('isExportFormatV1', () => {
|
||||||
|
it('should return true for v1 format', () => {
|
||||||
|
const obj = [{ id: 1 }];
|
||||||
|
expect(isExportFormatV1(obj)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-v1 formats', () => {
|
||||||
|
const obj = { version: 3, history: [], folders: [] };
|
||||||
|
expect(isExportFormatV1(obj)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isExportFormatV2', () => {
|
||||||
|
it('should return true for v2 format', () => {
|
||||||
|
const obj = { history: [], folders: [] };
|
||||||
|
expect(isExportFormatV2(obj)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-v2 formats', () => {
|
||||||
|
const obj = { version: 3, history: [], folders: [] };
|
||||||
|
expect(isExportFormatV2(obj)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isExportFormatV3', () => {
|
||||||
|
it('should return true for v3 format', () => {
|
||||||
|
const obj = { version: 3, history: [], folders: [] };
|
||||||
|
expect(isExportFormatV3(obj)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return false for non-v3 formats', () => {
|
||||||
|
const obj = { version: 4, history: [], folders: [] };
|
||||||
|
expect(isExportFormatV3(obj)).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleanData Functions', () => {
|
||||||
|
describe('cleaning v1 data', () => {
|
||||||
|
it('should return the latest format', () => {
|
||||||
|
const data = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'conversation 1',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: "what's up ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Hi',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
] as ExportFormatV1;
|
||||||
|
const obj = cleanData(data);
|
||||||
|
expect(isLatestExportFormat(obj)).toBe(true);
|
||||||
|
expect(obj).toEqual({
|
||||||
|
version: 3,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'conversation 1',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: "what's up ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Hi',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: OpenAIModels[OpenAIModelID.GPT_3_5],
|
||||||
|
prompt: DEFAULT_SYSTEM_PROMPT,
|
||||||
|
folderId: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('cleaning v2 data', () => {
|
||||||
|
it('should return the latest format', () => {
|
||||||
|
const data = {
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'conversation 1',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: "what's up ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Hi',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'folder 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
} as ExportFormatV2;
|
||||||
|
const obj = cleanData(data);
|
||||||
|
expect(isLatestExportFormat(obj)).toBe(true);
|
||||||
|
expect(obj).toEqual({
|
||||||
|
version: 3,
|
||||||
|
history: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'conversation 1',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: "what's up ?",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'assistant',
|
||||||
|
content: 'Hi',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
model: OpenAIModels[OpenAIModelID.GPT_3_5],
|
||||||
|
prompt: DEFAULT_SYSTEM_PROMPT,
|
||||||
|
folderId: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
folders: [
|
||||||
|
{
|
||||||
|
id: '1',
|
||||||
|
name: 'folder 1',
|
||||||
|
type: 'chat',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,5 +1,6 @@
|
||||||
import { Conversation } from '@/types/chat';
|
import { Conversation } from '@/types/chat';
|
||||||
import { KeyValuePair } from '@/types/data';
|
import { KeyValuePair } from '@/types/data';
|
||||||
|
import { SupportedExportFormats } from '@/types/export';
|
||||||
import { Folder } from '@/types/folder';
|
import { Folder } from '@/types/folder';
|
||||||
import {
|
import {
|
||||||
IconArrowBarLeft,
|
IconArrowBarLeft,
|
||||||
|
@ -36,10 +37,7 @@ interface Props {
|
||||||
onApiKeyChange: (apiKey: string) => void;
|
onApiKeyChange: (apiKey: string) => void;
|
||||||
onClearConversations: () => void;
|
onClearConversations: () => void;
|
||||||
onExportConversations: () => void;
|
onExportConversations: () => void;
|
||||||
onImportConversations: (data: {
|
onImportConversations: (data: SupportedExportFormats) => void;
|
||||||
conversations: Conversation[];
|
|
||||||
folders: Folder[];
|
|
||||||
}) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Chatbar: FC<Props> = ({
|
export const Chatbar: FC<Props> = ({
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Conversation } from '@/types/chat';
|
import { SupportedExportFormats } from '@/types/export';
|
||||||
import { Folder } from '@/types/folder';
|
|
||||||
import { IconFileExport, IconMoon, IconSun } from '@tabler/icons-react';
|
import { IconFileExport, IconMoon, IconSun } from '@tabler/icons-react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
|
@ -16,10 +15,7 @@ interface Props {
|
||||||
onApiKeyChange: (apiKey: string) => void;
|
onApiKeyChange: (apiKey: string) => void;
|
||||||
onClearConversations: () => void;
|
onClearConversations: () => void;
|
||||||
onExportConversations: () => void;
|
onExportConversations: () => void;
|
||||||
onImportConversations: (data: {
|
onImportConversations: (data: SupportedExportFormats) => void;
|
||||||
conversations: Conversation[];
|
|
||||||
folders: Folder[];
|
|
||||||
}) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatbarSettings: FC<Props> = ({
|
export const ChatbarSettings: FC<Props> = ({
|
||||||
|
|
|
@ -1,16 +1,11 @@
|
||||||
import { Conversation } from '@/types/chat';
|
import { SupportedExportFormats } from '@/types/export';
|
||||||
import { Folder } from '@/types/folder';
|
|
||||||
import { cleanConversationHistory } from '@/utils/app/clean';
|
|
||||||
import { IconFileImport } from '@tabler/icons-react';
|
import { IconFileImport } from '@tabler/icons-react';
|
||||||
import { useTranslation } from 'next-i18next';
|
import { useTranslation } from 'next-i18next';
|
||||||
import { FC } from 'react';
|
import { FC } from 'react';
|
||||||
import { SidebarButton } from '../Sidebar/SidebarButton';
|
import { SidebarButton } from '../Sidebar/SidebarButton';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
onImport: (data: {
|
onImport: (data: SupportedExportFormats) => void;
|
||||||
conversations: Conversation[];
|
|
||||||
folders: Folder[];
|
|
||||||
}) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Import: FC<Props> = ({ onImport }) => {
|
export const Import: FC<Props> = ({ onImport }) => {
|
||||||
|
@ -30,12 +25,7 @@ export const Import: FC<Props> = ({ onImport }) => {
|
||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = (e) => {
|
reader.onload = (e) => {
|
||||||
let json = JSON.parse(e.target?.result as string);
|
let json = JSON.parse(e.target?.result as string);
|
||||||
|
onImport(json);
|
||||||
if (json && !json.folders) {
|
|
||||||
json = { history: cleanConversationHistory(json), folders: [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
onImport({ conversations: json.history, folders: json.folders });
|
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import type { Config } from 'jest';
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
testEnvironment: 'jest-environment-jsdom',
|
||||||
|
verbose: true,
|
||||||
|
preset: 'ts-jest',
|
||||||
|
moduleNameMapper: {
|
||||||
|
'^@/(.*)$': '<rootDir>/$1',
|
||||||
|
},
|
||||||
|
reporters: [
|
||||||
|
'default',
|
||||||
|
[
|
||||||
|
'jest-junit',
|
||||||
|
{
|
||||||
|
outputDirectory: 'test-results/jest',
|
||||||
|
outputName: 'results.xml',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
|
@ -7,7 +7,8 @@
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write .",
|
||||||
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dqbd/tiktoken": "^1.0.2",
|
"@dqbd/tiktoken": "^1.0.2",
|
||||||
|
@ -29,6 +30,9 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.9",
|
"@tailwindcss/typography": "^0.5.9",
|
||||||
|
"@testing-library/jest-dom": "^5.16.5",
|
||||||
|
"@testing-library/react": "^14.0.0",
|
||||||
|
"@types/jest": "^29.5.0",
|
||||||
"@types/node": "18.15.0",
|
"@types/node": "18.15.0",
|
||||||
"@types/react": "18.0.28",
|
"@types/react": "18.0.28",
|
||||||
"@types/react-dom": "18.0.11",
|
"@types/react-dom": "18.0.11",
|
||||||
|
@ -37,10 +41,15 @@
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
"eslint": "8.36.0",
|
"eslint": "8.36.0",
|
||||||
"eslint-config-next": "13.2.4",
|
"eslint-config-next": "13.2.4",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"jest-environment-jsdom": "^29.5.0",
|
||||||
|
"jest-junit": "^15.0.0",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^2.8.7",
|
"prettier": "^2.8.7",
|
||||||
"prettier-plugin-tailwindcss": "^0.2.5",
|
"prettier-plugin-tailwindcss": "^0.2.5",
|
||||||
"tailwindcss": "^3.2.7",
|
"tailwindcss": "^3.2.7",
|
||||||
|
"ts-jest": "^29.0.5",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "4.9.5"
|
"typescript": "4.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Promptbar } from '@/components/Promptbar/Promptbar';
|
||||||
import { ChatBody, Conversation, Message } from '@/types/chat';
|
import { ChatBody, Conversation, Message } from '@/types/chat';
|
||||||
import { KeyValuePair } from '@/types/data';
|
import { KeyValuePair } from '@/types/data';
|
||||||
import { ErrorMessage } from '@/types/error';
|
import { ErrorMessage } from '@/types/error';
|
||||||
|
import { LatestExportFormat, SupportedExportFormats } from '@/types/export';
|
||||||
import { Folder, FolderType } from '@/types/folder';
|
import { Folder, FolderType } from '@/types/folder';
|
||||||
import { OpenAIModel, OpenAIModelID, OpenAIModels } from '@/types/openai';
|
import { OpenAIModel, OpenAIModelID, OpenAIModels } from '@/types/openai';
|
||||||
import { Prompt } from '@/types/prompt';
|
import { Prompt } from '@/types/prompt';
|
||||||
|
@ -285,19 +286,12 @@ const Home: React.FC<HomeProps> = ({ serverSideApiKeyIsSet }) => {
|
||||||
exportData();
|
exportData();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImportConversations = (data: {
|
const handleImportConversations = (data: SupportedExportFormats) => {
|
||||||
conversations: Conversation[];
|
const { history, folders }: LatestExportFormat = importData(data);
|
||||||
folders: Folder[];
|
|
||||||
}) => {
|
|
||||||
const updatedConversations = [...conversations, ...data.conversations];
|
|
||||||
const updatedFolders = [...folders, ...data.folders];
|
|
||||||
|
|
||||||
importData(updatedConversations, updatedFolders);
|
setConversations(history);
|
||||||
setConversations(updatedConversations);
|
setSelectedConversation(history[history.length - 1]);
|
||||||
setSelectedConversation(
|
setFolders(folders);
|
||||||
updatedConversations[updatedConversations.length - 1],
|
|
||||||
);
|
|
||||||
setFolders(updatedFolders);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectConversation = (conversation: Conversation) => {
|
const handleSelectConversation = (conversation: Conversation) => {
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
@ -15,9 +19,21 @@
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./*"]
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
}
|
"**/*.ts",
|
||||||
|
"**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"jest": {
|
||||||
|
"preset": "ts-jest",
|
||||||
|
"testEnvironment": "jsdom"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { Conversation, Message } from './chat';
|
||||||
|
import { Folder } from './folder';
|
||||||
|
import { OpenAIModel } from './openai';
|
||||||
|
|
||||||
|
export type SupportedExportFormats =
|
||||||
|
| ExportFormatV1
|
||||||
|
| ExportFormatV2
|
||||||
|
| ExportFormatV3;
|
||||||
|
export type LatestExportFormat = ExportFormatV3;
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
interface ConversationV1 {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
messages: Message[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExportFormatV1 = ConversationV1[];
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
interface ChatFolder {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExportFormatV2 {
|
||||||
|
history: Conversation[] | null;
|
||||||
|
folders: ChatFolder[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
export interface ExportFormatV3 {
|
||||||
|
version: 3;
|
||||||
|
history: Conversation[];
|
||||||
|
folders: Folder[];
|
||||||
|
}
|
|
@ -36,13 +36,18 @@ export const cleanSelectedConversation = (conversation: Conversation) => {
|
||||||
return updatedConversation;
|
return updatedConversation;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cleanConversationHistory = (history: Conversation[]) => {
|
export const cleanConversationHistory = (history: any[]): Conversation[] => {
|
||||||
// added model for each conversation (3/20/23)
|
// added model for each conversation (3/20/23)
|
||||||
// added system prompt for each conversation (3/21/23)
|
// added system prompt for each conversation (3/21/23)
|
||||||
// added folders (3/23/23)
|
// added folders (3/23/23)
|
||||||
// added prompts (3/26/23)
|
// added prompts (3/26/23)
|
||||||
|
|
||||||
return history.reduce((acc: Conversation[], conversation) => {
|
if (!Array.isArray(history)) {
|
||||||
|
console.warn('history is not an array. Returning an empty array.');
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return history.reduce((acc: any[], conversation) => {
|
||||||
try {
|
try {
|
||||||
if (!conversation.model) {
|
if (!conversation.model) {
|
||||||
conversation.model = OpenAIModels[OpenAIModelID.GPT_3_5];
|
conversation.model = OpenAIModels[OpenAIModelID.GPT_3_5];
|
||||||
|
|
|
@ -1,5 +1,53 @@
|
||||||
import { Conversation } from '@/types/chat';
|
import {
|
||||||
import { Folder } from '@/types/folder';
|
ExportFormatV1,
|
||||||
|
ExportFormatV2,
|
||||||
|
ExportFormatV3,
|
||||||
|
LatestExportFormat,
|
||||||
|
SupportedExportFormats,
|
||||||
|
} from '@/types/export';
|
||||||
|
import { cleanConversationHistory } from './clean';
|
||||||
|
|
||||||
|
export function isExportFormatV1(obj: any): obj is ExportFormatV1 {
|
||||||
|
return Array.isArray(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExportFormatV2(obj: any): obj is ExportFormatV2 {
|
||||||
|
return !('version' in obj) && 'folders' in obj && 'history' in obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isExportFormatV3(obj: any): obj is ExportFormatV3 {
|
||||||
|
return obj.version === 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isLatestExportFormat = isExportFormatV3;
|
||||||
|
|
||||||
|
export function cleanData(data: SupportedExportFormats): LatestExportFormat {
|
||||||
|
if (isExportFormatV1(data)) {
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
history: cleanConversationHistory(data),
|
||||||
|
folders: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExportFormatV2(data)) {
|
||||||
|
return {
|
||||||
|
version: 3,
|
||||||
|
history: cleanConversationHistory(data.history || []),
|
||||||
|
folders: (data.folders || []).map((chatFolder) => ({
|
||||||
|
id: chatFolder.id.toString(),
|
||||||
|
name: chatFolder.name,
|
||||||
|
type: 'chat',
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isExportFormatV3(data)) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('Unsupported data format');
|
||||||
|
}
|
||||||
|
|
||||||
function currentDate() {
|
function currentDate() {
|
||||||
const date = new Date();
|
const date = new Date();
|
||||||
|
@ -21,9 +69,10 @@ export const exportData = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
history,
|
version: 3,
|
||||||
folders,
|
history: history || [],
|
||||||
};
|
folders: folders || [],
|
||||||
|
} as LatestExportFormat;
|
||||||
|
|
||||||
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
const blob = new Blob([JSON.stringify(data, null, 2)], {
|
||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
|
@ -40,13 +89,18 @@ export const exportData = () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const importData = (
|
export const importData = (
|
||||||
conversations: Conversation[],
|
data: SupportedExportFormats,
|
||||||
folders: Folder[],
|
): LatestExportFormat => {
|
||||||
) => {
|
const cleanedData = cleanData(data);
|
||||||
|
|
||||||
|
const conversations = cleanedData.history;
|
||||||
localStorage.setItem('conversationHistory', JSON.stringify(conversations));
|
localStorage.setItem('conversationHistory', JSON.stringify(conversations));
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'selectedConversation',
|
'selectedConversation',
|
||||||
JSON.stringify(conversations[conversations.length - 1]),
|
JSON.stringify(conversations[conversations.length - 1]),
|
||||||
);
|
);
|
||||||
localStorage.setItem('folders', JSON.stringify(folders));
|
|
||||||
|
localStorage.setItem('folders', JSON.stringify(cleanedData.folders));
|
||||||
|
|
||||||
|
return cleanedData;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue