diff --git a/README.md b/README.md
index 2001d4d8878..bf7c3059413 100644
--- a/README.md
+++ b/README.md
@@ -12,15 +12,18 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
一键免费部署你的跨平台私人 ChatGPT 应用, 支持 GPT3, GPT4 & Gemini Pro 模型。
+[![Saas][Saas-image]][saas-url]
[![Web][Web-image]][web-url]
[![Windows][Windows-image]][download-url]
[![MacOS][MacOS-image]][download-url]
[![Linux][Linux-image]][download-url]
-[Web App](https://app.nextchat.dev/) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
+[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App](https://app.nextchat.dev) / [Desktop App](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [Discord](https://discord.gg/YCkeafCafC) / [Enterprise Edition](#enterprise-edition) / [Twitter](https://twitter.com/NextChatDev)
-[网页版](https://app.nextchat.dev/) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
+[NextChatAI](https://nextchat.dev/chat) / [网页版](https://app.nextchat.dev) / [客户端](https://github.com/Yidadaa/ChatGPT-Next-Web/releases) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [反馈](https://github.com/Yidadaa/ChatGPT-Next-Web/issues)
+[saas-url]: https://nextchat.dev/chat?utm_source=readme
+[saas-image]: https://img.shields.io/badge/NextChat-Saas-green?logo=microsoftedge
[web-url]: https://app.nextchat.dev/
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases
[Web-image]: https://img.shields.io/badge/Web-PWA-orange?logo=microsoftedge
@@ -172,7 +175,7 @@ We recommend that you follow the steps below to re-deploy:
### Enable Automatic Updates
-> If you encounter a failure of Upstream Sync execution, please manually sync fork once.
+> If you encounter a failure of Upstream Sync execution, please [manually update code](./README.md#manually-updating-code).
After forking the project, due to the limitations imposed by GitHub, you need to manually enable Workflows and Upstream Sync Action on the Actions page of the forked project. Once enabled, automatic updates will be scheduled every hour:
diff --git a/README_CN.md b/README_CN.md
index 7831e2ee981..73fbc3f51c4 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -8,7 +8,7 @@
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
-[企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) /[演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
+[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [企业版](#%E4%BC%81%E4%B8%9A%E7%89%88) / [演示 Demo](https://chat-gpt-next-web.vercel.app/) / [反馈 Issues](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [加入 Discord](https://discord.gg/zrhvHCr79N)
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [](https://zeabur.com/templates/ZBUEFA) [](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
@@ -54,7 +54,7 @@
### 打开自动更新
-> 如果你遇到了 Upstream Sync 执行错误,请手动 Sync Fork 一次!
+> 如果你遇到了 Upstream Sync 执行错误,请[手动 Sync Fork 一次](./README_CN.md#手动更新代码)!
当你 fork 项目之后,由于 Github 的限制,需要手动去你 fork 后的项目的 Actions 页面启用 Workflows,并启用 Upstream Sync Action,启用之后即可开启每小时定时自动更新:
diff --git a/README_JA.md b/README_JA.md
index 1716089af45..416928c2647 100644
--- a/README_JA.md
+++ b/README_JA.md
@@ -5,7 +5,7 @@
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
-[企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
+[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [企業版](#企業版) / [デモ](https://chat-gpt-next-web.vercel.app/) / [フィードバック](https://github.com/Yidadaa/ChatGPT-Next-Web/issues) / [Discordに参加](https://discord.gg/zrhvHCr79N)
[](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FChatGPTNextWeb%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=nextchat&repository-name=NextChat) [](https://zeabur.com/templates/ZBUEFA) [](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)
@@ -54,7 +54,7 @@
### 自動更新を開く
-> Upstream Sync の実行エラーが発生した場合は、手動で Sync Fork してください!
+> Upstream Sync の実行エラーが発生した場合は、[手動で Sync Fork](./README_JA.md#手動でコードを更新する) してください!
プロジェクトを fork した後、GitHub の制限により、fork 後のプロジェクトの Actions ページで Workflows を手動で有効にし、Upstream Sync Action を有効にする必要があります。有効化後、毎時の定期自動更新が可能になります:
diff --git a/app/api/google.ts b/app/api/google.ts
index e6ab472568b..707892c33d0 100644
--- a/app/api/google.ts
+++ b/app/api/google.ts
@@ -23,7 +23,8 @@ export async function handle(
});
}
- const bearToken = req.headers.get("Authorization") ?? "";
+ const bearToken =
+ req.headers.get("x-goog-api-key") || req.headers.get("Authorization") || "";
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
const apiKey = token ? token : serverConfig.googleApiKey;
@@ -91,8 +92,8 @@ async function request(req: NextRequest, apiKey: string) {
},
10 * 60 * 1000,
);
- const fetchUrl = `${baseUrl}${path}?key=${apiKey}${
- req?.nextUrl?.searchParams?.get("alt") === "sse" ? "&alt=sse" : ""
+ const fetchUrl = `${baseUrl}${path}${
+ req?.nextUrl?.searchParams?.get("alt") === "sse" ? "?alt=sse" : ""
}`;
console.log("[Fetch Url] ", fetchUrl);
@@ -100,6 +101,9 @@ async function request(req: NextRequest, apiKey: string) {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
+ "x-goog-api-key":
+ req.headers.get("x-goog-api-key") ||
+ (req.headers.get("Authorization") ?? "").replace("Bearer ", ""),
},
method: req.method,
body: req.body,
diff --git a/app/client/api.ts b/app/client/api.ts
index 8285b4d9f94..48bbde6bc81 100644
--- a/app/client/api.ts
+++ b/app/client/api.ts
@@ -272,7 +272,13 @@ export function getHeaders(ignoreHeaders: boolean = false) {
}
function getAuthHeader(): string {
- return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
+ return isAzure
+ ? "api-key"
+ : isAnthropic
+ ? "x-api-key"
+ : isGoogle
+ ? "x-goog-api-key"
+ : "Authorization";
}
const {
@@ -283,14 +289,15 @@ export function getHeaders(ignoreHeaders: boolean = false) {
apiKey,
isEnabledAccessControl,
} = getConfig();
- // when using google api in app, not set auth header
- if (isGoogle && clientConfig?.isApp) return headers;
// when using baidu api in app, not set auth header
if (isBaidu && clientConfig?.isApp) return headers;
const authHeader = getAuthHeader();
- const bearerToken = getBearerToken(apiKey, isAzure || isAnthropic);
+ const bearerToken = getBearerToken(
+ apiKey,
+ isAzure || isAnthropic || isGoogle,
+ );
if (bearerToken) {
headers[authHeader] = bearerToken;
diff --git a/app/client/platforms/google.ts b/app/client/platforms/google.ts
index ecb5ce44b57..3c2607271bf 100644
--- a/app/client/platforms/google.ts
+++ b/app/client/platforms/google.ts
@@ -48,10 +48,6 @@ export class GeminiProApi implements LLMApi {
let chatPath = [baseUrl, path].join("/");
chatPath += chatPath.includes("?") ? "&alt=sse" : "?alt=sse";
- // if chatPath.startsWith('http') then add key in query string
- if (chatPath.startsWith("http") && accessStore.googleApiKey) {
- chatPath += `&key=${accessStore.googleApiKey}`;
- }
return chatPath;
}
extractMessage(res: any) {
diff --git a/app/components/auth.module.scss b/app/components/auth.module.scss
index 6630c0613c7..fe143b4289b 100644
--- a/app/components/auth.module.scss
+++ b/app/components/auth.module.scss
@@ -1,12 +1,70 @@
.auth-page {
display: flex;
- justify-content: center;
+ justify-content: flex-start;
align-items: center;
height: 100%;
width: 100%;
flex-direction: column;
+ .top-banner {
+ position: relative;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 12px 64px;
+ box-sizing: border-box;
+ background: var(--second);
+ .top-banner-inner {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 14px;
+ line-height: 150%;
+ span {
+ gap: 8px;
+ a {
+ display: inline-flex;
+ align-items: center;
+ text-decoration: none;
+ margin-left: 8px;
+ color: var(--primary);
+ }
+ }
+ }
+ .top-banner-close {
+ cursor: pointer;
+ position: absolute;
+ top: 50%;
+ right: 48px;
+ transform: translateY(-50%);
+ }
+ }
+
+ @media (max-width: 600px) {
+ .top-banner {
+ padding: 12px 24px 12px 12px;
+ .top-banner-close {
+ right: 10px;
+ }
+ .top-banner-inner {
+ .top-banner-logo {
+ margin-right: 8px;
+ }
+ }
+ }
+ }
+
+ .auth-header {
+ display: flex;
+ justify-content: space-between;
+ width: 100%;
+ padding: 10px;
+ box-sizing: border-box;
+ animation: slide-in-from-top ease 0.3s;
+ }
.auth-logo {
+ margin-top: 10vh;
transform: scale(1.4);
}
@@ -14,6 +72,7 @@
font-size: 24px;
font-weight: bold;
line-height: 2;
+ margin-bottom: 1vh;
}
.auth-tips {
@@ -24,6 +83,10 @@
margin: 3vh 0;
}
+ .auth-input-second {
+ margin: 0 0 3vh 0;
+ }
+
.auth-actions {
display: flex;
justify-content: center;
diff --git a/app/components/auth.tsx b/app/components/auth.tsx
index 57118349bac..e19512d8741 100644
--- a/app/components/auth.tsx
+++ b/app/components/auth.tsx
@@ -1,21 +1,34 @@
import styles from "./auth.module.scss";
import { IconButton } from "./button";
-
+import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
-import { Path } from "../constant";
+import { Path, SAAS_CHAT_URL } from "../constant";
import { useAccessStore } from "../store";
import Locale from "../locales";
-
+import Delete from "../icons/close.svg";
+import Arrow from "../icons/arrow.svg";
+import Logo from "../icons/logo.svg";
+import { useMobileScreen } from "@/app/utils";
import BotIcon from "../icons/bot.svg";
-import { useEffect } from "react";
import { getClientConfig } from "../config/client";
+import LeftIcon from "@/app/icons/left.svg";
+import { safeLocalStorage } from "@/app/utils";
+import {
+ trackSettingsPageGuideToCPaymentClick,
+ trackAuthorizationPageButtonToCPaymentClick,
+} from "../utils/auth-settings-events";
+const storage = safeLocalStorage();
export function AuthPage() {
const navigate = useNavigate();
const accessStore = useAccessStore();
-
const goHome = () => navigate(Path.Home);
const goChat = () => navigate(Path.Chat);
+ const goSaas = () => {
+ trackAuthorizationPageButtonToCPaymentClick();
+ window.location.href = SAAS_CHAT_URL;
+ };
+
const resetAccessCode = () => {
accessStore.update((access) => {
access.openaiApiKey = "";
@@ -32,6 +45,14 @@ export function AuthPage() {
return (
+
+
+ }
+ text={Locale.Auth.Return}
+ onClick={() => navigate(Path.Home)}
+ >
+
@@ -65,7 +86,7 @@ export function AuthPage() {
}}
/>
{
- resetAccessCode();
- goHome();
+ goSaas();
}}
/>
);
}
+
+function TopBanner() {
+ const [isHovered, setIsHovered] = useState(false);
+ const [isVisible, setIsVisible] = useState(true);
+ const isMobile = useMobileScreen();
+ useEffect(() => {
+ // 检查 localStorage 中是否有标记
+ const bannerDismissed = storage.getItem("bannerDismissed");
+ // 如果标记不存在,存储默认值并显示横幅
+ if (!bannerDismissed) {
+ storage.setItem("bannerDismissed", "false");
+ setIsVisible(true); // 显示横幅
+ } else if (bannerDismissed === "true") {
+ // 如果标记为 "true",则隐藏横幅
+ setIsVisible(false);
+ }
+ }, []);
+
+ const handleMouseEnter = () => {
+ setIsHovered(true);
+ };
+
+ const handleMouseLeave = () => {
+ setIsHovered(false);
+ };
+
+ const handleClose = () => {
+ setIsVisible(false);
+ storage.setItem("bannerDismissed", "true");
+ };
+
+ if (!isVisible) {
+ return null;
+ }
+ return (
+
+
+ {(isHovered || isMobile) && (
+
+ )}
+
+ );
+}
diff --git a/app/components/button.module.scss b/app/components/button.module.scss
index e332df2d2c8..05248bee812 100644
--- a/app/components/button.module.scss
+++ b/app/components/button.module.scss
@@ -5,7 +5,6 @@
align-items: center;
justify-content: center;
padding: 10px;
-
cursor: pointer;
transition: all 0.3s ease;
overflow: hidden;
diff --git a/app/components/markdown.tsx b/app/components/markdown.tsx
index 4f1b0ed24ea..7d27f1d60fa 100644
--- a/app/components/markdown.tsx
+++ b/app/components/markdown.tsx
@@ -22,6 +22,8 @@ import {
import { useChatStore } from "../store";
import { IconButton } from "./button";
+import { useAppConfig } from "../store/config";
+
export function Mermaid(props: { code: string }) {
const ref = useRef(null);
const [hasError, setHasError] = useState(false);
@@ -92,7 +94,9 @@ export function PreCode(props: { children: any }) {
}
}, 600);
- const enableArtifacts = session.mask?.enableArtifacts !== false;
+ const config = useAppConfig();
+ const enableArtifacts =
+ session.mask?.enableArtifacts !== false && config.enableArtifacts;
//Wrap the paragraph for plain-text
useEffect(() => {
@@ -248,7 +252,7 @@ function tryWrapHtmlCode(text: string) {
},
)
.replace(
- /(<\/body>)([\r\n\s]*?)(<\/html>)([\n\r]*?)([`]*?)([\n\r]*?)/g,
+ /(<\/body>)([\r\n\s]*?)(<\/html>)([\n\r]*)([`]*)([\n\r]*?)/g,
(match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => {
return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match;
},
@@ -279,6 +283,20 @@ function _MarkDownContent(props: { content: string }) {
p: (pProps) => ,
a: (aProps) => {
const href = aProps.href || "";
+ if (/\.(aac|mp3|opus|wav)$/.test(href)) {
+ return (
+
+ );
+ }
+ if (/\.(3gp|3g2|webm|ogv|mpeg|mp4|avi)$/.test(href)) {
+ return (
+
+ );
+ }
const isInternal = /^\/#/i.test(href);
const target = isInternal ? "_self" : aProps.target ?? "_blank";
return ;
diff --git a/app/components/mask.tsx b/app/components/mask.tsx
index e4dd90826c7..c60e7a528fe 100644
--- a/app/components/mask.tsx
+++ b/app/components/mask.tsx
@@ -166,21 +166,23 @@ export function MaskConfig(props: {
>
-
- {
- props.updateMask((mask) => {
- mask.enableArtifacts = e.currentTarget.checked;
- });
- }}
- >
-
+ {globalConfig.enableArtifacts && (
+
+ {
+ props.updateMask((mask) => {
+ mask.enableArtifacts = e.currentTarget.checked;
+ });
+ }}
+ >
+
+ )}
{!props.shouldSyncFromGlobal ? (