diff --git a/README.md b/README.md
index 2001d4d88..be5e91d65 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) / [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
+[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 7831e2ee9..640fe3933 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) / [企业版](#%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 1716089af..ba3c514dc 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) / [企業版](#企業版) / [デモ](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 e6ab47256..7d3f08be4 100644
--- a/app/api/google.ts
+++ b/app/api/google.ts
@@ -91,7 +91,7 @@ async function request(req: NextRequest, apiKey: string) {
},
10 * 60 * 1000,
);
- const fetchUrl = `${baseUrl}${path}?key=${apiKey}${
+ const fetchUrl = `${baseUrl}${path}${
req?.nextUrl?.searchParams?.get("alt") === "sse" ? "&alt=sse" : ""
}`;
@@ -100,6 +100,9 @@ async function request(req: NextRequest, apiKey: string) {
headers: {
"Content-Type": "application/json",
"Cache-Control": "no-store",
+ "x-google-api-key":
+ req.headers.get("x-google-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 8285b4d9f..48bbde6bc 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 ecb5ce44b..3c2607271 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 6630c0613..fe143b428 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 57118349b..e19512d87 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 e332df2d2..05248bee8 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 b57fd7490..6e340d065 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(() => {
@@ -128,8 +132,9 @@ export function PreCode(props: { children: any }) {
className="copy-code-button"
onClick={() => {
if (ref.current) {
- const code = ref.current.innerText;
- copyToClipboard(code);
+ copyToClipboard(
+ ref.current.querySelector("code")?.innerText ?? "",
+ );
}
}}
>
@@ -278,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 e4dd90826..c60e7a528 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 ? (