From c0afd5957e035e34774bdcb21a64a5dd77c94440 Mon Sep 17 00:00:00 2001 From: "qcgm197874@gmail.com" Date: Wed, 9 Aug 2023 18:47:51 +0800 Subject: [PATCH 01/10] Feat: Support data sync * including online sync using MongoDB Atlas and local file sync --- package.json | 1 + src/background.js | 25 ++++- src/components/ChatSetting.vue | 159 +++++++++++++++++++++++--------- src/components/ProxySetting.vue | 2 +- src/i18n/locales/en.json | 9 +- src/i18n/locales/zh.json | 11 ++- src/store/index.js | 4 + src/utils/index.js | 1 + src/utils/storage.js | 47 ++++++++++ 9 files changed, 210 insertions(+), 49 deletions(-) create mode 100644 src/utils/index.js create mode 100644 src/utils/storage.js diff --git a/package.json b/package.json index debec37fa8..061e149605 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "husky": "^8.0.3", "lint-staged": "^13.2.3", "md5": "^2.3.0", + "mongodb": "^5.7.0", "prettier": "^3.0.0", "prettier-plugin-vue": "^1.1.6", "sse.js": "github:mpetazzoni/sse.js" diff --git a/src/background.js b/src/background.js index 5c772180c1..306a56a120 100644 --- a/src/background.js +++ b/src/background.js @@ -5,6 +5,7 @@ import { createProtocol } from "vue-cli-plugin-electron-builder/lib"; import installExtension, { VUEJS3_DEVTOOLS } from "electron-devtools-installer"; import fs from "fs"; import path from "path"; +const MongoClient = require("mongodb").MongoClient; import updateApp from "./update"; const isDevelopment = process.env.NODE_ENV !== "production"; @@ -310,12 +311,34 @@ ipcMain.handle("save-proxy-setting", async (event, args) => { }); }); -ipcMain.handle("save-proxy-and-restart", async () => { +ipcMain.handle("restart-app", async () => { app.relaunch(); app.exit(); return ""; }); // Proxy Setting End +ipcMain.handle("upload", async (event, { MongoDB_URL, data }) => { + // eslint-disable-next-line no-debugger + debugger; + const client = new MongoClient(MongoDB_URL); + + client.connect(); + + const collection = client.db("test").collection("dialogs"); + // const chunks = chunkify(data); // 将大对象拆分成块 + collection.insertOne(data); +}); +ipcMain.handle("download", async (event, MongoDB_URL) => { + // eslint-disable-next-line no-debugger + // debugger; + const client = new MongoClient(MongoDB_URL); + + client.connect(); + + const collection = client.db("test").collection("dialogs"); + const results = await collection.find().toArray(); + return results; +}); nativeTheme.on("updated", () => { mainWindow.webContents.send("on-updated-system-theme"); diff --git a/src/components/ChatSetting.vue b/src/components/ChatSetting.vue index 30469f06cf..4e090ef008 100644 --- a/src/components/ChatSetting.vue +++ b/src/components/ChatSetting.vue @@ -1,5 +1,6 @@ @@ -21,59 +55,94 @@ import { ref } from "vue"; import { useStore } from "vuex"; import i18n from "@/i18n"; +const electron = window.require("electron"); +const ipcRenderer = electron.ipcRenderer; import ConfirmModal from "@/components/ConfirmModal.vue"; -import bots from "@/bots"; +import { get_messages } from "@/utils"; + const emit = defineEmits(["close-dialog"]); const confirmModal = ref(); const store = useStore(); +const jsonData = ref(null); +const MongoDB_URL = ref(store.state.MongoDB_URL); + +const setMongoDBURL = (url) => { + store.commit("setMongoDBURL", url); +}; +async function upload() { + const result = await confirmModal.value.showModal( + "", + i18n.global.t("chat.confirmUpload"), + ); + if (result) { + const data = JSON.parse(JSON.stringify(localStorage, null, 2)); + await ipcRenderer.invoke("upload", { + MongoDB_URL: MongoDB_URL.value, + data, + }); + } +} +async function download() { + const result = await confirmModal.value.showModal( + "", + i18n.global.t("chat.confirmDownload"), + ); + if (result) { + // eslint-disable-next-line + const value = await ipcRenderer.invoke("download", MongoDB_URL.value); + // jsonData.value = value; + // eslint-disable-next-line no-debugger + // debugger; + reload(value[0]); + } +} +const readJson = async (event) => { + const reader = new FileReader(); + reader.onload = (evt) => { + const value = JSON.parse(evt.target.result); + jsonData.value = value; + reload(value); + }; + reader.readAsText(event.target.files[0]); +}; +async function reload(value) { + const load = i18n.global.t("proxy.saveAndApply"); + const result = await confirmModal.value.showModal("", `${load}?`); + if (result) { + // let value_messages = JSON.parse(value["chatall-messages"]); + // const local_chats = JSON.parse(localStorage["chatall-messages"]).chats; + // value_messages.chats.forEach((element) => { + // element.index += local_chats.length; + // }); + // value_messages.chats = [...local_chats, ...value_chats.chats]; + // value["chatall-messages"] = JSON.stringify(value_messages.chats); + Object.keys(value).map((d) => (localStorage[d] = value[d])); + await ipcRenderer.invoke("restart-app"); + } +} // This function downloads the chat history as a JSON file. + const downloadJson = () => { - // Get the chat history from localStorage. - const chatallMessages = localStorage.getItem("chatall-messages"); - if (!chatallMessages) { + const messages = get_messages(); + if (!messages) { console.error("chatall-messages not found in localStorage"); return; } - const chats = JSON.parse(chatallMessages)?.chats ?? []; - // Create an array of messages from the chat history. - const messages = chats - .filter((d) => !d.hide) - .map((chat) => ({ - // The title of the chat. - title: chat.title, - // The messages in the chat. - messages: chat.messages - .filter((d) => !d.hide) - .reduce((arr, message) => { - const t = message.type; - const content = message.content; - if (t == "prompt") { - arr.push({ - prompt: content, - responses: [], - }); - } else { - const botClassname = message.className; - const bot = bots.getBotByClassName(botClassname); - const botName = bot.getFullname(); - arr.at(-1).responses.push({ - content, - botName, - botClassname, - botModel: message.model, - highlight: message.highlight, - }); - } - return arr; - }, []), - })); - - // Create a blob that contains the JSON data. - // The space parameter specifies the indentation of nested objects in the string representation. - const blob = new Blob([JSON.stringify({ chats: messages }, null, 2)], { + const content = "history"; + download_by_link(messages, content); +}; +const downloadDataJson = () => { + const content = "data"; + const messages = localStorage; + download_by_link(messages, content); +}; +// Create a blob that contains the JSON data. +// The space parameter specifies the indentation of nested objects in the string representation. +function download_by_link(messages, name) { + const blob = new Blob([JSON.stringify(messages, null, 2)], { // The type of the blob. type: "application/json", }); @@ -88,7 +157,7 @@ const downloadJson = () => { const hour = String(date.getHours()).padStart(2, "0"); const minute = String(date.getMinutes()).padStart(2, "0"); const second = String(date.getSeconds()).padStart(2, "0"); - const fileName = `chatall-history-${year}${month}${day}-${hour}${minute}${second}`; + const fileName = `chatall-${name}-${year}${month}${day}-${hour}${minute}${second}`; const a = document.createElement("a"); a.href = url; @@ -103,7 +172,8 @@ const downloadJson = () => { // Revoke the URL for the blob. URL.revokeObjectURL(url); -}; +} + async function deleteChats() { const confirm = await confirmModal.value.showModal( "", @@ -115,3 +185,4 @@ async function deleteChats() { } } +@/utils/storage diff --git a/src/components/ProxySetting.vue b/src/components/ProxySetting.vue index 53dcb2dda2..0e56c8d47e 100644 --- a/src/components/ProxySetting.vue +++ b/src/components/ProxySetting.vue @@ -280,7 +280,7 @@ async function saveAndActive() { ); if (result) { await onlySave(); - await ipcRenderer.invoke("save-proxy-and-restart"); + await ipcRenderer.invoke("restart-app"); } } diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 10ed7c4f78..3e71b43cdd 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -52,7 +52,14 @@ "newChat": "New Chat", "deleteAllChatHistory": "Delete All Chat History", "downloadAllChatHistory": "Save All Chat History", - "confirmDeleteAllChatHistory": "Are you sure you want to delete all chat history? This action cannot be undone." + "confirmDeleteAllChatHistory": "Are you sure you want to delete all chat history? This action cannot be undone.", + "backupToLocal": "Backup all data to local", + "restoreFromLocal": "Restore from backup", + "addressExample": "mongodb+srv://user:password@cluster0.***.mongodb.net/?retryWrites=true", + "upload": "Upload", + "download": "Download", + "confirmUpload": "Are you sure you want to upload all chat history?", + "confirmDownload": "Are you sure you want to download all chat history?" }, "bot": { "creatingConversation": "Creating conversation...", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 43384f1979..954d5c0f82 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -52,7 +52,13 @@ "newChat": "新对话", "deleteAllChatHistory": "删除所有对话记录", "downloadAllChatHistory": "保存所有对话记录", - "confirmDeleteAllChatHistory": "你确定要删除所有对话记录吗?此操作无法撤消。" + "confirmDeleteAllChatHistory": "你确定要删除所有对话记录吗?此操作无法撤消。", + "backupToLocal": "备份所有数据到本地", + "restoreFromLocal": "从备份恢复", + "upload": "上传", + "download": "下载", + "confirmUpload": "你确定要上传所有对话记录吗?", + "confirmDownload": "你确定要下载所有对话记录吗?" }, "bot": { "creatingConversation": "创建新对话...", @@ -253,7 +259,8 @@ "itemsPerPageAll": "全部" }, "input": { - "clear": "清除" + "clear": "清除", + "prependAction": "预设操作" } }, "10": "10", diff --git a/src/store/index.js b/src/store/index.js index 1e93f2ffbe..52036d1ab7 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -40,6 +40,7 @@ export default createStore({ azureOpenAIApiDeploymentName: "", azureOpenAIApiVersion: "", }, + MongoDB_URL: "", chatgpt: { refreshCycle: 0, riskConfirmed: false, @@ -324,6 +325,9 @@ export default createStore({ state.chats = newChats; state.currentChatIndex = 0; }, + setMongoDBURL(state, url) { + state.MongoDB_URL = url; + }, addPrompt(state, values) { const addPrompt = { ...values }; addPrompt.index = state.prompts.push(addPrompt) - 1; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 0000000000..79610fa455 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1 @@ +export { get_messages } from "./storage"; diff --git a/src/utils/storage.js b/src/utils/storage.js new file mode 100644 index 0000000000..d1114b59f1 --- /dev/null +++ b/src/utils/storage.js @@ -0,0 +1,47 @@ +import bots from "@/bots"; +export function get_messages() { + // Get the chat history from localStorage. + const chatallMessages = localStorage.getItem("chatall-messages"); + if (!chatallMessages) { + return; + } + + const chats = JSON.parse(chatallMessages)?.chats ?? []; + try { + // Create an array of messages from the chat history. + const messages = chats + .filter((d) => !d.hide) + .map((chat) => ({ + // The title of the chat. + title: chat.title, + // The messages in the chat. + messages: chat.messages + .filter((d) => !d.hide) + .reduce((arr, message) => { + const t = message.type; + const content = message.content; + if (t == "prompt") { + arr.push({ + prompt: content, + responses: [], + }); + } else { + const botClassname = message.className; + const bot = bots.getBotByClassName(botClassname); + const botName = bot.getFullname(); + arr.at(-1).responses.push({ + content, + botName, + botClassname, + botModel: message.model, + highlight: message.highlight, + }); + } + return arr; + }, []), + })); + return messages; + } catch (e) { + // debugger; + } +} From a816868c225d9bc844c591d1bdff2af2e369a959 Mon Sep 17 00:00:00 2001 From: "qcgm197874@gmail.com" Date: Wed, 9 Aug 2023 19:15:55 +0800 Subject: [PATCH 02/10] fix warning; i18n --- src/background.js | 2 +- src/components/ChatSetting.vue | 14 ++++++++++---- src/i18n/locales/en.json | 2 +- src/i18n/locales/zh.json | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/background.js b/src/background.js index 306a56a120..8a70bde830 100644 --- a/src/background.js +++ b/src/background.js @@ -319,7 +319,7 @@ ipcMain.handle("restart-app", async () => { // Proxy Setting End ipcMain.handle("upload", async (event, { MongoDB_URL, data }) => { // eslint-disable-next-line no-debugger - debugger; + // debugger; const client = new MongoClient(MongoDB_URL); client.connect(); diff --git a/src/components/ChatSetting.vue b/src/components/ChatSetting.vue index 4e090ef008..77a1cf1559 100644 --- a/src/components/ChatSetting.vue +++ b/src/components/ChatSetting.vue @@ -34,17 +34,23 @@ > - MongoDB Atlas + {{ $t("chat.MongoDBAtlas") }} - + {{ $t("chat.upload") }} - + {{ $t("chat.download") }} diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 3e71b43cdd..243da1b43e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -55,7 +55,7 @@ "confirmDeleteAllChatHistory": "Are you sure you want to delete all chat history? This action cannot be undone.", "backupToLocal": "Backup all data to local", "restoreFromLocal": "Restore from backup", - "addressExample": "mongodb+srv://user:password@cluster0.***.mongodb.net/?retryWrites=true", + "MongoDBAtlas":"MongoDB Atlas", "upload": "Upload", "download": "Download", "confirmUpload": "Are you sure you want to upload all chat history?", diff --git a/src/i18n/locales/zh.json b/src/i18n/locales/zh.json index 954d5c0f82..f027a80a4a 100644 --- a/src/i18n/locales/zh.json +++ b/src/i18n/locales/zh.json @@ -55,6 +55,7 @@ "confirmDeleteAllChatHistory": "你确定要删除所有对话记录吗?此操作无法撤消。", "backupToLocal": "备份所有数据到本地", "restoreFromLocal": "从备份恢复", + "MongoDBAtlas":"MongoDB 云数据库", "upload": "上传", "download": "下载", "confirmUpload": "你确定要上传所有对话记录吗?", From 82abb15fd954b21d144ed75cfef645f63e941e40 Mon Sep 17 00:00:00 2001 From: "qcgm197874@gmail.com" Date: Wed, 9 Aug 2023 19:20:45 +0800 Subject: [PATCH 03/10] comments --- src/components/ChatSetting.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ChatSetting.vue b/src/components/ChatSetting.vue index 77a1cf1559..c2b8e0c79a 100644 --- a/src/components/ChatSetting.vue +++ b/src/components/ChatSetting.vue @@ -115,6 +115,7 @@ async function reload(value) { const load = i18n.global.t("proxy.saveAndApply"); const result = await confirmModal.value.showModal("", `${load}?`); if (result) { + // todo merge data if supporting merge local and new data // let value_messages = JSON.parse(value["chatall-messages"]); // const local_chats = JSON.parse(localStorage["chatall-messages"]).chats; // value_messages.chats.forEach((element) => { From b584ade9f9bcb62c8f7199423a0c4ccb87f72946 Mon Sep 17 00:00:00 2001 From: "qcgm197874@gmail.com" Date: Thu, 10 Aug 2023 16:00:48 +0800 Subject: [PATCH 04/10] handle promise and prompt success --- src/background.js | 23 +++++++++++++++++------ src/components/ChatSetting.vue | 26 +++++++++++++++++++++++--- 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/background.js b/src/background.js index 8a70bde830..03bcc50284 100644 --- a/src/background.js +++ b/src/background.js @@ -321,12 +321,23 @@ ipcMain.handle("upload", async (event, { MongoDB_URL, data }) => { // eslint-disable-next-line no-debugger // debugger; const client = new MongoClient(MongoDB_URL); - - client.connect(); - - const collection = client.db("test").collection("dialogs"); - // const chunks = chunkify(data); // 将大对象拆分成块 - collection.insertOne(data); + return client + .connect() + .then(() => { + // eslint-disable-next-line no-debugger + // debugger; + const collection = client.db("test").collection("dialogs"); + // const chunks = chunkify(data); // 将大对象拆分成块 + return collection.insertOne(data); + }) + .then(() => { + // eslint-disable-next-line no-debugger + // debugger; + return true; + }) + .catch(() => { + return false; + }); }); ipcMain.handle("download", async (event, MongoDB_URL) => { // eslint-disable-next-line no-debugger diff --git a/src/components/ChatSetting.vue b/src/components/ChatSetting.vue index c2b8e0c79a..bc9096bfa4 100644 --- a/src/components/ChatSetting.vue +++ b/src/components/ChatSetting.vue @@ -55,10 +55,17 @@ + + {{ snackbar.text }} +