mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-08 02:56:39 +08:00
Merge c234160b82
into 85704570f3
This commit is contained in:
commit
7f307bc299
@ -23,6 +23,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
.import-config-modal {
|
||||
.import-config-content {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-prompt-modal {
|
||||
min-height: 40vh;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { useState, useEffect, useMemo } from "react";
|
||||
|
||||
import styles from "./settings.module.scss";
|
||||
import uiStyle from "./ui-lib.module.scss";
|
||||
|
||||
import ResetIcon from "../icons/reload.svg";
|
||||
import AddIcon from "../icons/add.svg";
|
||||
@ -80,6 +81,7 @@ import { useSyncStore } from "../store/sync";
|
||||
import { nanoid } from "nanoid";
|
||||
import { useMaskStore } from "../store/mask";
|
||||
import { ProviderType } from "../utils/cloud";
|
||||
import CancelIcon from "../icons/cancel.svg";
|
||||
|
||||
function EditPromptModal(props: { id: string; onClose: () => void }) {
|
||||
const promptStore = usePromptStore();
|
||||
@ -477,6 +479,61 @@ function SyncConfigModal(props: { onClose?: () => void }) {
|
||||
);
|
||||
}
|
||||
|
||||
function ImportConfigModal(props: { onClose?: () => void; rows?: number }) {
|
||||
const [importString, setImportString] = useState("");
|
||||
const syncStore = useSyncStore();
|
||||
|
||||
return (
|
||||
<div className="modal-mask">
|
||||
<Modal
|
||||
title={Locale.Settings.Sync.Config.ImportModal.Title}
|
||||
onClose={() => props.onClose?.()}
|
||||
actions={[
|
||||
<IconButton
|
||||
key="cancel"
|
||||
onClick={() => {
|
||||
props.onClose?.();
|
||||
}}
|
||||
icon={<CancelIcon />}
|
||||
bordered
|
||||
shadow
|
||||
text={Locale.UI.Cancel}
|
||||
/>,
|
||||
<IconButton
|
||||
key="confirm"
|
||||
onClick={async () => {
|
||||
try {
|
||||
await syncStore.importSyncConfig(importString);
|
||||
showToast(Locale.Settings.Sync.ImportSuccess);
|
||||
props.onClose?.();
|
||||
} catch (e) {
|
||||
showToast(Locale.Settings.Sync.ImportFail);
|
||||
console.log("[Sync] Failed to import sync config", e);
|
||||
}
|
||||
}}
|
||||
icon={<ConfirmIcon />}
|
||||
bordered
|
||||
text={Locale.UI.Confirm}
|
||||
/>,
|
||||
]}
|
||||
>
|
||||
<div className={styles["import-config-modal"]}>
|
||||
<textarea
|
||||
className={uiStyle["modal-input"]}
|
||||
autoFocus
|
||||
placeholder={Locale.Settings.Sync.Config.ImportModal.Placeholder}
|
||||
value={importString}
|
||||
rows={props?.rows ?? 3}
|
||||
onInput={(e) => {
|
||||
setImportString(e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SyncItems() {
|
||||
const syncStore = useSyncStore();
|
||||
const chatStore = useChatStore();
|
||||
@ -487,6 +544,7 @@ function SyncItems() {
|
||||
}, [syncStore]);
|
||||
|
||||
const [showSyncConfigModal, setShowSyncConfigModal] = useState(false);
|
||||
const [showImportModal, setShowImportModal] = useState(false);
|
||||
|
||||
const stateOverview = useMemo(() => {
|
||||
const sessions = chatStore.sessions;
|
||||
@ -522,7 +580,15 @@ function SyncItems() {
|
||||
setShowSyncConfigModal(true);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<DownloadIcon />}
|
||||
text={Locale.UI.Import}
|
||||
onClick={() => {
|
||||
setShowImportModal(true);
|
||||
}}
|
||||
/>
|
||||
{couldSync && (
|
||||
<>
|
||||
<IconButton
|
||||
icon={<ResetIcon />}
|
||||
text={Locale.UI.Sync}
|
||||
@ -536,6 +602,14 @@ function SyncItems() {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
icon={<UploadIcon />}
|
||||
text={Locale.UI.Export}
|
||||
onClick={() => {
|
||||
syncStore.exportSyncConfig();
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ListItem>
|
||||
@ -568,6 +642,10 @@ function SyncItems() {
|
||||
{showSyncConfigModal && (
|
||||
<SyncConfigModal onClose={() => setShowSyncConfigModal(false)} />
|
||||
)}
|
||||
|
||||
{showImportModal && (
|
||||
<ImportConfigModal onClose={() => setShowImportModal(false)} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -196,12 +196,19 @@ const cn = {
|
||||
NotSyncYet: "还没有进行过同步",
|
||||
Success: "同步成功",
|
||||
Fail: "同步失败",
|
||||
|
||||
ExportSuccess: "同步配置已复制到剪贴板",
|
||||
ExportFail: "同步配置导出失败,请重试",
|
||||
ImportSuccess: "同步配置导入成功",
|
||||
ImportFail: "同步配置导入失败,请检查配置字符串",
|
||||
Config: {
|
||||
Modal: {
|
||||
Title: "配置云同步",
|
||||
Check: "检查可用性",
|
||||
},
|
||||
ImportModal: {
|
||||
Title: "导入同步配置",
|
||||
Placeholder: "请输入同步配置",
|
||||
},
|
||||
SyncType: {
|
||||
Title: "同步类型",
|
||||
SubTitle: "选择喜爱的同步服务器",
|
||||
|
@ -199,12 +199,19 @@ const en: LocaleType = {
|
||||
NotSyncYet: "Not sync yet",
|
||||
Success: "Sync Success",
|
||||
Fail: "Sync Fail",
|
||||
|
||||
ExportSuccess: "Sync config copied to clipboard",
|
||||
ExportFail: "Export failed, please retry",
|
||||
ImportSuccess: "Sync config imported successfully",
|
||||
ImportFail: "Import failed, please check config string",
|
||||
Config: {
|
||||
Modal: {
|
||||
Title: "Config Sync",
|
||||
Check: "Check Connection",
|
||||
},
|
||||
ImportModal: {
|
||||
Title: "Import Sync Config",
|
||||
Placeholder: "Enter sync config",
|
||||
},
|
||||
SyncType: {
|
||||
Title: "Sync Type",
|
||||
SubTitle: "Choose your favorite sync service",
|
||||
|
@ -1,3 +1,4 @@
|
||||
import pako from "pako";
|
||||
import { getClientConfig } from "../config/client";
|
||||
import { Updater } from "../typing";
|
||||
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
||||
@ -44,10 +45,52 @@ const DEFAULT_SYNC_STATE = {
|
||||
lastSyncTime: 0,
|
||||
lastProvider: "",
|
||||
};
|
||||
|
||||
export const useSyncStore = createPersistStore(
|
||||
DEFAULT_SYNC_STATE,
|
||||
(set, get) => ({
|
||||
async exportSyncConfig() {
|
||||
const currentProvider = get().provider;
|
||||
const exportData = {
|
||||
provider: currentProvider,
|
||||
config: get()[currentProvider],
|
||||
useProxy: get().useProxy,
|
||||
proxyUrl: get().proxyUrl,
|
||||
};
|
||||
|
||||
const jsonString = JSON.stringify(exportData);
|
||||
const compressed = pako.deflate(jsonString);
|
||||
const encoded = btoa(
|
||||
String.fromCharCode.apply(null, Array.from(compressed)),
|
||||
);
|
||||
try {
|
||||
await navigator.clipboard.writeText(encoded);
|
||||
showToast(Locale.Settings.Sync.ExportSuccess);
|
||||
} catch (e) {
|
||||
console.log("[Sync] failed to copy", e);
|
||||
showToast(Locale.Settings.Sync.ExportFail);
|
||||
}
|
||||
},
|
||||
importSyncConfig(encodedString: string) {
|
||||
try {
|
||||
const decoded = atob(encodedString);
|
||||
const decompressed = pako.inflate(
|
||||
new Uint8Array(decoded.split("").map((char) => char.charCodeAt(0))),
|
||||
{ to: "string" },
|
||||
);
|
||||
const importedData = JSON.parse(decompressed);
|
||||
|
||||
set({
|
||||
provider: importedData.provider,
|
||||
[importedData.provider]: importedData.config,
|
||||
useProxy: importedData.useProxy,
|
||||
proxyUrl: importedData.proxyUrl,
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("[Sync] failed to set sync config", e);
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
cloudSync() {
|
||||
const config = get()[get().provider];
|
||||
return Object.values(config).every((c) => c.toString().length > 0);
|
||||
@ -100,8 +143,10 @@ export const useSyncStore = createPersistStore(
|
||||
const remoteState = await client.get(config.username);
|
||||
if (!remoteState || remoteState === "") {
|
||||
await client.set(config.username, JSON.stringify(localState));
|
||||
console.log("[Sync] Remote state is empty, using local state instead.");
|
||||
return
|
||||
console.log(
|
||||
"[Sync] Remote state is empty, using local state instead.",
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
const parsedRemoteState = JSON.parse(
|
||||
await client.get(config.username),
|
||||
|
@ -34,6 +34,7 @@
|
||||
"nanoid": "^5.0.3",
|
||||
"next": "^14.1.1",
|
||||
"node-fetch": "^3.3.1",
|
||||
"pako": "^2.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
@ -52,6 +53,7 @@
|
||||
"@tauri-apps/cli": "1.5.11",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^20.11.30",
|
||||
"@types/pako": "^2.0.3",
|
||||
"@types/react": "^18.2.70",
|
||||
"@types/react-dom": "^18.2.7",
|
||||
"@types/react-katex": "^3.0.0",
|
||||
|
10
yarn.lock
10
yarn.lock
@ -1735,6 +1735,11 @@
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
"@types/pako@^2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmmirror.com/@types/pako/-/pako-2.0.3.tgz#b6993334f3af27c158f3fe0dfeeba987c578afb1"
|
||||
integrity sha512-bq0hMV9opAcrmE0Byyo0fY3Ew4tgOevJmQ9grUhpXQhYfyLJ1Kqg3P33JT5fdbT2AjeAjR51zqqVjAL/HMkx7Q==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
@ -5199,6 +5204,11 @@ p-map@^4.0.0:
|
||||
dependencies:
|
||||
aggregate-error "^3.0.0"
|
||||
|
||||
pako@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmmirror.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
|
||||
integrity sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
Loading…
Reference in New Issue
Block a user