forked from XiaoMo/ChatGPT-Next-Web
Merge branch 'main' of https://github.com/Yidadaa/ChatGPT-Next-Web
This commit is contained in:
commit
5933b3d7eb
@ -15,7 +15,8 @@ export function AuthPage() {
|
|||||||
const access = useAccessStore();
|
const access = useAccessStore();
|
||||||
|
|
||||||
const goHome = () => navigate(Path.Home);
|
const goHome = () => navigate(Path.Home);
|
||||||
const resetAccessCode = () => access.updateCode(""); // Reset access code to empty string
|
const goChat = () => navigate(Path.Chat);
|
||||||
|
const resetAccessCode = () => { access.updateCode(""); access.updateToken(""); }; // Reset access code to empty string
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (getClientConfig()?.isApp) {
|
if (getClientConfig()?.isApp) {
|
||||||
@ -42,17 +43,34 @@ export function AuthPage() {
|
|||||||
access.updateCode(e.currentTarget.value);
|
access.updateCode(e.currentTarget.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
{!access.hideUserApiKey ? (
|
||||||
|
<>
|
||||||
|
<div className={styles["auth-tips"]}>{Locale.Auth.SubTips}</div>
|
||||||
|
<input
|
||||||
|
className={styles["auth-input"]}
|
||||||
|
type="password"
|
||||||
|
placeholder={Locale.Settings.Token.Placeholder}
|
||||||
|
value={access.token}
|
||||||
|
onChange={(e) => {
|
||||||
|
access.updateToken(e.currentTarget.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<div className={styles["auth-actions"]}>
|
<div className={styles["auth-actions"]}>
|
||||||
<IconButton
|
<IconButton
|
||||||
text={Locale.Auth.Confirm}
|
text={Locale.Auth.Confirm}
|
||||||
type="primary"
|
type="primary"
|
||||||
onClick={goHome}
|
onClick={goChat}
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
text={Locale.Auth.Later}
|
||||||
|
onClick={() => {
|
||||||
|
resetAccessCode();
|
||||||
|
goHome();
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<IconButton text={Locale.Auth.Later} onClick={() => {
|
|
||||||
resetAccessCode();
|
|
||||||
goHome();
|
|
||||||
}} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -433,25 +433,55 @@ export function ImagePreviewer(props: {
|
|||||||
|
|
||||||
const isMobile = useMobileScreen();
|
const isMobile = useMobileScreen();
|
||||||
|
|
||||||
const download = () => {
|
const download = async () => {
|
||||||
showToast(Locale.Export.Image.Toast);
|
showToast(Locale.Export.Image.Toast);
|
||||||
const dom = previewRef.current;
|
const dom = previewRef.current;
|
||||||
if (!dom) return;
|
if (!dom) return;
|
||||||
toPng(dom)
|
|
||||||
.then((blob) => {
|
|
||||||
if (!blob) return;
|
|
||||||
|
|
||||||
if (isMobile || getClientConfig()?.isApp) {
|
const isApp = getClientConfig()?.isApp;
|
||||||
showImageModal(blob);
|
|
||||||
|
try {
|
||||||
|
const blob = await toPng(dom);
|
||||||
|
if (!blob) return;
|
||||||
|
|
||||||
|
if (isMobile || (isApp && window.__TAURI__)) {
|
||||||
|
if (isApp && window.__TAURI__) {
|
||||||
|
const result = await window.__TAURI__.dialog.save({
|
||||||
|
defaultPath: `${props.topic}.png`,
|
||||||
|
filters: [
|
||||||
|
{
|
||||||
|
name: "PNG Files",
|
||||||
|
extensions: ["png"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "All Files",
|
||||||
|
extensions: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
const response = await fetch(blob);
|
||||||
|
const buffer = await response.arrayBuffer();
|
||||||
|
const uint8Array = new Uint8Array(buffer);
|
||||||
|
await window.__TAURI__.fs.writeBinaryFile(result, uint8Array);
|
||||||
|
showToast(Locale.Download.Success);
|
||||||
|
} else {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const link = document.createElement("a");
|
showImageModal(blob);
|
||||||
link.download = `${props.topic}.png`;
|
|
||||||
link.href = blob;
|
|
||||||
link.click();
|
|
||||||
refreshPreview();
|
|
||||||
}
|
}
|
||||||
})
|
} else {
|
||||||
.catch((e) => console.log("[Export Image] ", e));
|
const link = document.createElement("a");
|
||||||
|
link.download = `${props.topic}.png`;
|
||||||
|
link.href = blob;
|
||||||
|
link.click();
|
||||||
|
refreshPreview();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshPreview = () => {
|
const refreshPreview = () => {
|
||||||
|
12
app/global.d.ts
vendored
12
app/global.d.ts
vendored
@ -13,5 +13,17 @@ declare module "*.svg";
|
|||||||
declare interface Window {
|
declare interface Window {
|
||||||
__TAURI__?: {
|
__TAURI__?: {
|
||||||
writeText(text: string): Promise<void>;
|
writeText(text: string): Promise<void>;
|
||||||
|
invoke(command: string, payload?: Record<string, unknown>): Promise<any>;
|
||||||
|
dialog: {
|
||||||
|
save(options?: Record<string, unknown>): Promise<string | null>;
|
||||||
|
};
|
||||||
|
fs: {
|
||||||
|
writeBinaryFile(path: string, data: Uint8Array): Promise<void>;
|
||||||
|
};
|
||||||
|
notification:{
|
||||||
|
requestPermission(): Promise<Permission>;
|
||||||
|
isPermissionGranted(): Promise<boolean>;
|
||||||
|
sendNotification(options: string | Options): void;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ const ar: PartialLocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "تحتاج إلى رمز الوصول",
|
Title: "تحتاج إلى رمز الوصول",
|
||||||
Tips: "يرجى إدخال رمز الوصول أدناه",
|
Tips: "يرجى إدخال رمز الوصول أدناه",
|
||||||
|
SubTips: "أو أدخل مفتاح واجهة برمجة تطبيقات OpenAI الخاص بك",
|
||||||
Input: "رمز الوصول",
|
Input: "رمز الوصول",
|
||||||
Confirm: "تأكيد",
|
Confirm: "تأكيد",
|
||||||
Later: "لاحقًا",
|
Later: "لاحقًا",
|
||||||
|
@ -10,6 +10,7 @@ const bn: PartialLocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
Title: "একটি অ্যাক্সেস কোড প্রয়োজন",
|
||||||
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
Tips: "নীচে অ্যাক্সেস কোড ইনপুট করুন",
|
||||||
|
SubTips: "অথবা আপনার OpenAI API কী প্রবেশ করুন",
|
||||||
Input: "অ্যাক্সেস কোড",
|
Input: "অ্যাক্সেস কোড",
|
||||||
Confirm: "নিশ্চিত করুন",
|
Confirm: "নিশ্চিত করুন",
|
||||||
Later: "পরে",
|
Later: "পরে",
|
||||||
|
@ -13,6 +13,7 @@ const cn = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "需要密码",
|
Title: "需要密码",
|
||||||
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
Tips: "管理员开启了密码验证,请在下方填入访问码",
|
||||||
|
SubTips: "或者输入你的 OpenAI API 密钥",
|
||||||
Input: "在此处填写访问码",
|
Input: "在此处填写访问码",
|
||||||
Confirm: "确认",
|
Confirm: "确认",
|
||||||
Later: "稍后再说",
|
Later: "稍后再说",
|
||||||
@ -323,6 +324,10 @@ const cn = {
|
|||||||
Success: "已写入剪切板",
|
Success: "已写入剪切板",
|
||||||
Failed: "复制失败,请赋予剪切板权限",
|
Failed: "复制失败,请赋予剪切板权限",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "内容已下载到您的目录。",
|
||||||
|
Failed: "下载失败。",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `包含 ${x} 条预设提示词`,
|
Toast: (x: any) => `包含 ${x} 条预设提示词`,
|
||||||
Edit: "当前对话设置",
|
Edit: "当前对话设置",
|
||||||
|
@ -15,6 +15,7 @@ const en: LocaleType = {
|
|||||||
Auth: {
|
Auth: {
|
||||||
Title: "Need Access Code",
|
Title: "Need Access Code",
|
||||||
Tips: "Please enter access code below",
|
Tips: "Please enter access code below",
|
||||||
|
SubTips: "Or enter your OpenAI API Key",
|
||||||
Input: "access code",
|
Input: "access code",
|
||||||
Confirm: "Confirm",
|
Confirm: "Confirm",
|
||||||
Later: "Later",
|
Later: "Later",
|
||||||
@ -329,6 +330,10 @@ const en: LocaleType = {
|
|||||||
Success: "Copied to clipboard",
|
Success: "Copied to clipboard",
|
||||||
Failed: "Copy failed, please grant permission to access clipboard",
|
Failed: "Copy failed, please grant permission to access clipboard",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "Content downloaded to your directory.",
|
||||||
|
Failed: "Download failed.",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `With ${x} contextual prompts`,
|
Toast: (x: any) => `With ${x} contextual prompts`,
|
||||||
Edit: "Current Chat Settings",
|
Edit: "Current Chat Settings",
|
||||||
|
@ -4,12 +4,12 @@ import { PartialLocaleType } from "./index";
|
|||||||
const id: PartialLocaleType = {
|
const id: PartialLocaleType = {
|
||||||
WIP: "Coming Soon...",
|
WIP: "Coming Soon...",
|
||||||
Error: {
|
Error: {
|
||||||
Unauthorized:
|
Unauthorized: "Akses tidak diizinkan, silakan masukkan kode akses atau masukkan kunci API OpenAI Anda. di halaman [autentikasi](/#/auth) atau di halaman [Pengaturan](/#/settings).",
|
||||||
"Akses tidak diizinkan. Silakan [otorisasi](/#/auth) dengan memasukkan kode akses.",
|
|
||||||
},
|
},
|
||||||
Auth: {
|
Auth: {
|
||||||
Title: "Diperlukan Kode Akses",
|
Title: "Diperlukan Kode Akses",
|
||||||
Tips: "Masukkan kode akses di bawah",
|
Tips: "Masukkan kode akses di bawah",
|
||||||
|
SubTips: "Atau masukkan kunci API OpenAI Anda",
|
||||||
Input: "Kode Akses",
|
Input: "Kode Akses",
|
||||||
Confirm: "Konfirmasi",
|
Confirm: "Konfirmasi",
|
||||||
Later: "Nanti",
|
Later: "Nanti",
|
||||||
@ -301,6 +301,10 @@ const id: PartialLocaleType = {
|
|||||||
Failed:
|
Failed:
|
||||||
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
|
"Gagal menyalin, mohon berikan izin untuk mengakses clipboard atau Clipboard API tidak didukung (Tauri)",
|
||||||
},
|
},
|
||||||
|
Download: {
|
||||||
|
Success: "Konten berhasil diunduh ke direktori Anda.",
|
||||||
|
Failed: "Unduhan gagal.",
|
||||||
|
},
|
||||||
Context: {
|
Context: {
|
||||||
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
|
Toast: (x: any) => `Dengan ${x} promp kontekstual`,
|
||||||
Edit: "Pengaturan Obrolan Saat Ini",
|
Edit: "Pengaturan Obrolan Saat Ini",
|
||||||
|
@ -7,13 +7,13 @@ const tw: PartialLocaleType = {
|
|||||||
Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
|
Unauthorized: "目前您的狀態是未授權,請前往[設定頁面](/#/auth)輸入授權碼。",
|
||||||
},
|
},
|
||||||
ChatItem: {
|
ChatItem: {
|
||||||
ChatItemCount: (count: number) => `${count} 條對話`,
|
ChatItemCount: (count: number) => `${count} 則對話`,
|
||||||
},
|
},
|
||||||
Chat: {
|
Chat: {
|
||||||
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 條對話`,
|
SubTitle: (count: number) => `您已經與 ChatGPT 進行了 ${count} 則對話`,
|
||||||
Actions: {
|
Actions: {
|
||||||
ChatList: "查看訊息列表",
|
ChatList: "檢視訊息列表",
|
||||||
CompressedHistory: "查看壓縮後的歷史 Prompt",
|
CompressedHistory: "檢視壓縮後的歷史 Prompt",
|
||||||
Export: "匯出聊天紀錄",
|
Export: "匯出聊天紀錄",
|
||||||
Copy: "複製",
|
Copy: "複製",
|
||||||
Stop: "停止",
|
Stop: "停止",
|
||||||
@ -23,15 +23,15 @@ const tw: PartialLocaleType = {
|
|||||||
Rename: "重新命名對話",
|
Rename: "重新命名對話",
|
||||||
Typing: "正在輸入…",
|
Typing: "正在輸入…",
|
||||||
Input: (submitKey: string) => {
|
Input: (submitKey: string) => {
|
||||||
var inputHints = `輸入訊息後,按下 ${submitKey} 鍵即可發送`;
|
var inputHints = `輸入訊息後,按下 ${submitKey} 鍵即可傳送`;
|
||||||
if (submitKey === String(SubmitKey.Enter)) {
|
if (submitKey === String(SubmitKey.Enter)) {
|
||||||
inputHints += ",Shift + Enter 鍵換行";
|
inputHints += ",Shift + Enter 鍵換行";
|
||||||
}
|
}
|
||||||
return inputHints;
|
return inputHints;
|
||||||
},
|
},
|
||||||
Send: "發送",
|
Send: "傳送",
|
||||||
Config: {
|
Config: {
|
||||||
Reset: "重置預設",
|
Reset: "重設",
|
||||||
SaveAs: "另存新檔",
|
SaveAs: "另存新檔",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -46,7 +46,7 @@ const tw: PartialLocaleType = {
|
|||||||
Title: "上下文記憶 Prompt",
|
Title: "上下文記憶 Prompt",
|
||||||
EmptyContent: "尚未記憶",
|
EmptyContent: "尚未記憶",
|
||||||
Copy: "複製全部",
|
Copy: "複製全部",
|
||||||
Send: "發送記憶",
|
Send: "傳送記憶",
|
||||||
Reset: "重設對話",
|
Reset: "重設對話",
|
||||||
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
ResetConfirm: "重設後將清除目前對話記錄以及歷史記憶,確認重設?",
|
||||||
},
|
},
|
||||||
@ -71,22 +71,22 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
InjectSystemPrompts: {
|
InjectSystemPrompts: {
|
||||||
Title: "匯入系統提示",
|
Title: "匯入系統提示",
|
||||||
SubTitle: "強制在每個請求的訊息列表開頭添加一個模擬 ChatGPT 的系統提示",
|
SubTitle: "強制在每個請求的訊息列表開頭新增一個模擬 ChatGPT 的系統提示",
|
||||||
},
|
},
|
||||||
Update: {
|
Update: {
|
||||||
Version: (x: string) => `當前版本:${x}`,
|
Version: (x: string) => `目前版本:${x}`,
|
||||||
IsLatest: "已是最新版本",
|
IsLatest: "已是最新版本",
|
||||||
CheckUpdate: "檢查更新",
|
CheckUpdate: "檢查更新",
|
||||||
IsChecking: "正在檢查更新...",
|
IsChecking: "正在檢查更新...",
|
||||||
FoundUpdate: (x: string) => `發現新版本:${x}`,
|
FoundUpdate: (x: string) => `發現新版本:${x}`,
|
||||||
GoToUpdate: "前往更新",
|
GoToUpdate: "前往更新",
|
||||||
},
|
},
|
||||||
SendKey: "發送鍵",
|
SendKey: "傳送鍵",
|
||||||
Theme: "主題",
|
Theme: "主題",
|
||||||
TightBorder: "緊湊邊框",
|
TightBorder: "緊湊邊框",
|
||||||
SendPreviewBubble: {
|
SendPreviewBubble: {
|
||||||
Title: "預覽氣泡",
|
Title: "預覽氣泡",
|
||||||
SubTitle: "在預覽氣泡中預覽 Markdown 内容",
|
SubTitle: "在預覽氣泡中預覽 Markdown 內容",
|
||||||
},
|
},
|
||||||
Mask: {
|
Mask: {
|
||||||
Splash: {
|
Splash: {
|
||||||
@ -101,7 +101,7 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
List: "自定義提示詞列表",
|
List: "自定義提示詞列表",
|
||||||
ListCount: (builtin: number, custom: number) =>
|
ListCount: (builtin: number, custom: number) =>
|
||||||
`內建 ${builtin} 條,用戶定義 ${custom} 條`,
|
`內建 ${builtin} 條,使用者定義 ${custom} 條`,
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Modal: {
|
Modal: {
|
||||||
Title: "提示詞列表",
|
Title: "提示詞列表",
|
||||||
@ -132,7 +132,7 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
IsChecking: "正在檢查…",
|
IsChecking: "正在檢查…",
|
||||||
Check: "重新檢查",
|
Check: "重新檢查",
|
||||||
NoAccess: "輸入API Key查看餘額",
|
NoAccess: "輸入 API Key 檢視餘額",
|
||||||
},
|
},
|
||||||
AccessCode: {
|
AccessCode: {
|
||||||
Title: "授權碼",
|
Title: "授權碼",
|
||||||
@ -150,7 +150,7 @@ const tw: PartialLocaleType = {
|
|||||||
},
|
},
|
||||||
PresencePenalty: {
|
PresencePenalty: {
|
||||||
Title: "話題新穎度 (presence_penalty)",
|
Title: "話題新穎度 (presence_penalty)",
|
||||||
SubTitle: "值越大,越有可能擴展到新話題",
|
SubTitle: "值越大,越有可能拓展到新話題",
|
||||||
},
|
},
|
||||||
FrequencyPenalty: {
|
FrequencyPenalty: {
|
||||||
Title: "頻率懲罰度 (frequency_penalty)",
|
Title: "頻率懲罰度 (frequency_penalty)",
|
||||||
@ -163,7 +163,7 @@ const tw: PartialLocaleType = {
|
|||||||
Error: "出錯了,請稍後再嘗試",
|
Error: "出錯了,請稍後再嘗試",
|
||||||
Prompt: {
|
Prompt: {
|
||||||
History: (content: string) =>
|
History: (content: string) =>
|
||||||
"這是 AI 與用戶的歷史聊天總結,作為前情提要:" + content,
|
"這是 AI 與使用者的歷史聊天總結,作為前情提要:" + content,
|
||||||
Topic:
|
Topic:
|
||||||
"Use the language used by the user (e.g. en for english conversation, zh-hant for chinese conversation, etc.) to generate a title (at most 6 words) summarizing our conversation without any lead-in, quotation marks, preamble like 'Title:', direct text copies, single-word replies, quotation marks, translations, or brackets. Remove enclosing quotation marks. The title should make third-party grasp the essence of the conversation in first sight.",
|
"Use the language used by the user (e.g. en for english conversation, zh-hant for chinese conversation, etc.) to generate a title (at most 6 words) summarizing our conversation without any lead-in, quotation marks, preamble like 'Title:', direct text copies, single-word replies, quotation marks, translations, or brackets. Remove enclosing quotation marks. The title should make third-party grasp the essence of the conversation in first sight.",
|
||||||
Summarize:
|
Summarize:
|
||||||
@ -192,16 +192,16 @@ const tw: PartialLocaleType = {
|
|||||||
Item: {
|
Item: {
|
||||||
Info: (count: number) => `包含 ${count} 條預設對話`,
|
Info: (count: number) => `包含 ${count} 條預設對話`,
|
||||||
Chat: "對話",
|
Chat: "對話",
|
||||||
View: "查看",
|
View: "檢視",
|
||||||
Edit: "編輯",
|
Edit: "編輯",
|
||||||
Delete: "删除",
|
Delete: "刪除",
|
||||||
DeleteConfirm: "確認删除?",
|
DeleteConfirm: "確認刪除?",
|
||||||
},
|
},
|
||||||
EditModal: {
|
EditModal: {
|
||||||
Title: (readonly: boolean) =>
|
Title: (readonly: boolean) =>
|
||||||
`編輯預設面具 ${readonly ? "(只读)" : ""}`,
|
`編輯預設面具 ${readonly ? "(只讀)" : ""}`,
|
||||||
Download: "下載預設",
|
Download: "下載預設",
|
||||||
Clone: "克隆預設",
|
Clone: "複製預設",
|
||||||
},
|
},
|
||||||
Config: {
|
Config: {
|
||||||
Avatar: "角色頭像",
|
Avatar: "角色頭像",
|
||||||
@ -215,7 +215,7 @@ const tw: PartialLocaleType = {
|
|||||||
SubTitle: "現在開始,與面具背後的靈魂思維碰撞",
|
SubTitle: "現在開始,與面具背後的靈魂思維碰撞",
|
||||||
More: "搜尋更多",
|
More: "搜尋更多",
|
||||||
NotShow: "不再呈現",
|
NotShow: "不再呈現",
|
||||||
ConfirmNoShow: "確認禁用?禁用後可以随時在設定中重新啟用。",
|
ConfirmNoShow: "確認停用?停用後可以隨時在設定中重新啟用。",
|
||||||
},
|
},
|
||||||
UI: {
|
UI: {
|
||||||
Confirm: "確認",
|
Confirm: "確認",
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { LLMModel } from "../client/api";
|
import { LLMModel } from "../client/api";
|
||||||
|
import { isMacOS } from "../utils";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import {
|
import {
|
||||||
DEFAULT_INPUT_TEMPLATE,
|
DEFAULT_INPUT_TEMPLATE,
|
||||||
@ -27,7 +28,7 @@ export enum Theme {
|
|||||||
export const DEFAULT_CONFIG = {
|
export const DEFAULT_CONFIG = {
|
||||||
lastUpdate: Date.now(), // timestamp, to merge state
|
lastUpdate: Date.now(), // timestamp, to merge state
|
||||||
|
|
||||||
submitKey: SubmitKey.CtrlEnter as SubmitKey,
|
submitKey: isMacOS() ? SubmitKey.MetaEnter : SubmitKey.CtrlEnter,
|
||||||
avatar: "1f603",
|
avatar: "1f603",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
theme: Theme.Auto as Theme,
|
theme: Theme.Auto as Theme,
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { getClientConfig } from "../config/client";
|
||||||
import { Updater } from "../typing";
|
import { Updater } from "../typing";
|
||||||
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
import { ApiPath, STORAGE_KEY, StoreKey } from "../constant";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
@ -20,6 +21,7 @@ export interface WebDavConfig {
|
|||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
export type SyncStore = GetStoreState<typeof useSyncStore>;
|
export type SyncStore = GetStoreState<typeof useSyncStore>;
|
||||||
|
|
||||||
const DEFAULT_SYNC_STATE = {
|
const DEFAULT_SYNC_STATE = {
|
||||||
@ -57,7 +59,11 @@ export const useSyncStore = createPersistStore(
|
|||||||
|
|
||||||
export() {
|
export() {
|
||||||
const state = getLocalAppState();
|
const state = getLocalAppState();
|
||||||
const fileName = `Backup-${new Date().toLocaleString()}.json`;
|
const datePart = isApp
|
||||||
|
? `${new Date().toLocaleDateString().replace(/\//g, '_')} ${new Date().toLocaleTimeString().replace(/:/g, '_')}`
|
||||||
|
: new Date().toLocaleString();
|
||||||
|
|
||||||
|
const fileName = `Backup-${datePart}.json`;
|
||||||
downloadAs(JSON.stringify(state), fileName);
|
downloadAs(JSON.stringify(state), fileName);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -2,8 +2,11 @@ import { FETCH_COMMIT_URL, FETCH_TAG_URL, StoreKey } from "../constant";
|
|||||||
import { api } from "../client/api";
|
import { api } from "../client/api";
|
||||||
import { getClientConfig } from "../config/client";
|
import { getClientConfig } from "../config/client";
|
||||||
import { createPersistStore } from "../utils/store";
|
import { createPersistStore } from "../utils/store";
|
||||||
|
import ChatGptIcon from "../icons/chatgpt.png";
|
||||||
|
import Locale from "../locales";
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
const ONE_MINUTE = 60 * 1000;
|
||||||
|
const isApp = !!getClientConfig()?.isApp;
|
||||||
|
|
||||||
function formatVersionDate(t: string) {
|
function formatVersionDate(t: string) {
|
||||||
const d = new Date(+t);
|
const d = new Date(+t);
|
||||||
@ -80,6 +83,38 @@ export const useUpdateStore = createPersistStore(
|
|||||||
set(() => ({
|
set(() => ({
|
||||||
remoteVersion: remoteId,
|
remoteVersion: remoteId,
|
||||||
}));
|
}));
|
||||||
|
if (window.__TAURI__?.notification && isApp) {
|
||||||
|
// Check if notification permission is granted
|
||||||
|
await window.__TAURI__?.notification.isPermissionGranted().then((granted) => {
|
||||||
|
if (!granted) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
// Request permission to show notifications
|
||||||
|
window.__TAURI__?.notification.requestPermission().then((permission) => {
|
||||||
|
if (permission === 'granted') {
|
||||||
|
if (version === remoteId) {
|
||||||
|
// Show a notification using Tauri
|
||||||
|
window.__TAURI__?.notification.sendNotification({
|
||||||
|
title: "ChatGPT Next Web",
|
||||||
|
body: `${Locale.Settings.Update.IsLatest}`,
|
||||||
|
icon: `${ChatGptIcon.src}`,
|
||||||
|
sound: "Default"
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const updateMessage = Locale.Settings.Update.FoundUpdate(`${remoteId}`);
|
||||||
|
// Show a notification for the new version using Tauri
|
||||||
|
window.__TAURI__?.notification.sendNotification({
|
||||||
|
title: "ChatGPT Next Web",
|
||||||
|
body: updateMessage,
|
||||||
|
icon: `${ChatGptIcon.src}`,
|
||||||
|
sound: "Default"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
console.log("[Got Upstream] ", remoteId);
|
console.log("[Got Upstream] ", remoteId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[Fetch Upstream Commit Id]", error);
|
console.error("[Fetch Upstream Commit Id]", error);
|
||||||
|
55
app/utils.ts
55
app/utils.ts
@ -31,12 +31,41 @@ export async function copyToClipboard(text: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadAs(text: string, filename: string) {
|
export async function downloadAs(text: string, filename: string) {
|
||||||
const element = document.createElement("a");
|
if (window.__TAURI__) {
|
||||||
element.setAttribute(
|
const result = await window.__TAURI__.dialog.save({
|
||||||
"href",
|
defaultPath: `${filename}`,
|
||||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
|
filters: [
|
||||||
);
|
{
|
||||||
|
name: `${filename.split('.').pop()} files`,
|
||||||
|
extensions: [`${filename.split('.').pop()}`],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "All Files",
|
||||||
|
extensions: ["*"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result !== null) {
|
||||||
|
try {
|
||||||
|
await window.__TAURI__.fs.writeBinaryFile(
|
||||||
|
result,
|
||||||
|
new Uint8Array([...text].map((c) => c.charCodeAt(0)))
|
||||||
|
);
|
||||||
|
showToast(Locale.Download.Success);
|
||||||
|
} catch (error) {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showToast(Locale.Download.Failed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const element = document.createElement("a");
|
||||||
|
element.setAttribute(
|
||||||
|
"href",
|
||||||
|
"data:text/plain;charset=utf-8," + encodeURIComponent(text),
|
||||||
|
);
|
||||||
element.setAttribute("download", filename);
|
element.setAttribute("download", filename);
|
||||||
|
|
||||||
element.style.display = "none";
|
element.style.display = "none";
|
||||||
@ -46,7 +75,7 @@ export function downloadAs(text: string, filename: string) {
|
|||||||
|
|
||||||
document.body.removeChild(element);
|
document.body.removeChild(element);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
export function readFromFile() {
|
export function readFromFile() {
|
||||||
return new Promise<string>((res, rej) => {
|
return new Promise<string>((res, rej) => {
|
||||||
const fileInput = document.createElement("input");
|
const fileInput = document.createElement("input");
|
||||||
@ -173,3 +202,15 @@ export function autoGrowTextArea(dom: HTMLTextAreaElement) {
|
|||||||
export function getCSSVar(varName: string) {
|
export function getCSSVar(varName: string) {
|
||||||
return getComputedStyle(document.body).getPropertyValue(varName).trim();
|
return getComputedStyle(document.body).getPropertyValue(varName).trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detects Macintosh
|
||||||
|
*/
|
||||||
|
export function isMacOS(): boolean {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
let userAgent = window.navigator.userAgent.toLocaleLowerCase();
|
||||||
|
const macintosh = /iphone|ipad|ipod|macintosh/.test(userAgent)
|
||||||
|
return !!macintosh
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -17,7 +17,7 @@ tauri-build = { version = "1.3.0", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tauri = { version = "1.3.0", features = ["clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
tauri = { version = "1.3.0", features = ["notification-all", "fs-all", "clipboard-all", "dialog-all", "shell-open", "updater", "window-close", "window-hide", "window-maximize", "window-minimize", "window-set-icon", "window-set-ignore-cursor-events", "window-set-resizable", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] }
|
||||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "ChatGPT Next Web",
|
"productName": "ChatGPT Next Web",
|
||||||
"version": "2.9.7"
|
"version": "2.9.8"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
@ -44,6 +44,12 @@
|
|||||||
"startDragging": true,
|
"startDragging": true,
|
||||||
"unmaximize": true,
|
"unmaximize": true,
|
||||||
"unminimize": true
|
"unminimize": true
|
||||||
|
},
|
||||||
|
"fs": {
|
||||||
|
"all": true
|
||||||
|
},
|
||||||
|
"notification": {
|
||||||
|
"all": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bundle": {
|
"bundle": {
|
||||||
|
Loading…
Reference in New Issue
Block a user