mirror of
https://github.com/Yidadaa/ChatGPT-Next-Web.git
synced 2025-09-04 14:36:54 +08:00
Compare commits
24 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
13c1d2fd2b | ||
|
e35c807216 | ||
|
5a2cc6f154 | ||
|
ef3e8e6fac | ||
|
b558d1afc6 | ||
|
ddfd05b008 | ||
|
d2ad01a9ff | ||
|
64a17abfe2 | ||
|
04b638aa06 | ||
|
31e30906d0 | ||
|
bc00be9065 | ||
|
4a599e986f | ||
|
f1ca03e378 | ||
|
f3d5fc7a84 | ||
|
3bfcdf9c41 | ||
|
144200e315 | ||
|
398e229c77 | ||
|
6a61fe5776 | ||
|
9835206452 | ||
|
70b0580fb7 | ||
|
23eb7732d7 | ||
|
26e50cefea | ||
|
588e907181 | ||
|
eae7d6260f |
@@ -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>
|
||||||
);
|
);
|
||||||
|
@@ -937,7 +937,7 @@ function _Chat() {
|
|||||||
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
|
const isTouchTopEdge = e.scrollTop <= edgeThreshold;
|
||||||
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
|
const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold;
|
||||||
const isHitBottom =
|
const isHitBottom =
|
||||||
bottomHeight >= e.scrollHeight - (isMobileScreen ? 0 : 10);
|
bottomHeight >= e.scrollHeight - (isMobileScreen ? 4 : 10);
|
||||||
|
|
||||||
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
|
const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE;
|
||||||
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
|
const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE;
|
||||||
@@ -1155,7 +1155,13 @@ function _Chat() {
|
|||||||
{isUser ? (
|
{isUser ? (
|
||||||
<Avatar avatar={config.avatar} />
|
<Avatar avatar={config.avatar} />
|
||||||
) : (
|
) : (
|
||||||
<MaskAvatar mask={session.mask} />
|
<>
|
||||||
|
{["system"].includes(message.role) ? (
|
||||||
|
<Avatar avatar="2699-fe0f" />
|
||||||
|
) : (
|
||||||
|
<MaskAvatar mask={session.mask} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</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) => {
|
const isApp = getClientConfig()?.isApp;
|
||||||
if (!blob) return;
|
|
||||||
|
try {
|
||||||
if (isMobile || getClientConfig()?.isApp) {
|
const blob = await toPng(dom);
|
||||||
showImageModal(blob);
|
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 = () => {
|
||||||
|
@@ -115,7 +115,10 @@ const loadAsyncGoogleFont = () => {
|
|||||||
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
getClientConfig()?.buildMode === "export" ? remoteFontUrl : proxyFontUrl;
|
||||||
linkEl.rel = "stylesheet";
|
linkEl.rel = "stylesheet";
|
||||||
linkEl.href =
|
linkEl.href =
|
||||||
googleFontUrl + "/css2?family=" + encodeURIComponent("Noto Sans:wght@300;400;700;900") + "&display=swap";
|
googleFontUrl +
|
||||||
|
"/css2?family=" +
|
||||||
|
encodeURIComponent("Noto Sans:wght@300;400;700;900") +
|
||||||
|
"&display=swap";
|
||||||
document.head.appendChild(linkEl);
|
document.head.appendChild(linkEl);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -125,6 +128,8 @@ function Screen() {
|
|||||||
const isHome = location.pathname === Path.Home;
|
const isHome = location.pathname === Path.Home;
|
||||||
const isAuth = location.pathname === Path.Auth;
|
const isAuth = location.pathname === Path.Auth;
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
|
const shouldTightBorder =
|
||||||
|
config.tightBorder && !isMobileScreen && !getClientConfig()?.isApp;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAsyncGoogleFont();
|
loadAsyncGoogleFont();
|
||||||
@@ -134,11 +139,9 @@ function Screen() {
|
|||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
styles.container +
|
styles.container +
|
||||||
` ${
|
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
|
||||||
config.tightBorder && !isMobileScreen
|
getLang() === "ar" ? styles["rtl-screen"] : ""
|
||||||
? styles["tight-container"]
|
}`
|
||||||
: styles.container
|
|
||||||
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isAuth ? (
|
{isAuth ? (
|
||||||
|
@@ -160,7 +160,13 @@ export function SideBar(props: { className?: string }) {
|
|||||||
icon={<MaskIcon />}
|
icon={<MaskIcon />}
|
||||||
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
text={shouldNarrow ? undefined : Locale.Mask.Name}
|
||||||
className={styles["sidebar-bar-button"]}
|
className={styles["sidebar-bar-button"]}
|
||||||
onClick={() => navigate(Path.NewChat, { state: { fromHome: true } })}
|
onClick={() => {
|
||||||
|
if (config.dontShowMaskSplashScreen !== true) {
|
||||||
|
navigate(Path.NewChat, { state: { fromHome: true } });
|
||||||
|
} else {
|
||||||
|
navigate(Path.Masks, { state: { fromHome: true } });
|
||||||
|
}
|
||||||
|
}}
|
||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
<IconButton
|
<IconButton
|
||||||
|
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",
|
||||||
|
@@ -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
|
||||||
|
}
|
||||||
|
@@ -21,7 +21,7 @@ export function createWebDavClient(store: SyncStore) {
|
|||||||
proxyUrl,
|
proxyUrl,
|
||||||
});
|
});
|
||||||
console.log("[WebDav] check", res.status, res.statusText);
|
console.log("[WebDav] check", res.status, res.statusText);
|
||||||
return [201, 200, 404, 401].includes(res.status);
|
return [201, 200, 404, 301, 302, 307, 308].includes(res.status);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[WebDav] failed to check", e);
|
console.error("[WebDav] failed to check", e);
|
||||||
}
|
}
|
||||||
|
@@ -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": {
|
||||||
|
Reference in New Issue
Block a user