diff --git a/README.md b/README.md index 2001d4d88..c72c791b2 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 diff --git a/README_CN.md b/README_CN.md index 7831e2ee9..c5d02477c 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) [Deploy on Zeabur](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) [Deploy on Zeabur](https://zeabur.com/templates/ZBUEFA) [Open in Gitpod](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) diff --git a/README_JA.md b/README_JA.md index 1716089af..2b0a3ab78 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) [Zeaburでデプロイ](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) [Zeaburでデプロイ](https://zeabur.com/templates/ZBUEFA) [Gitpodで開く](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) 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 ( +
+
+ + + {Locale.Auth.TopTips} + { + trackSettingsPageGuideToCPaymentClick(); + }} + > + {Locale.Settings.Access.SaasStart.ChatNow} + + + +
+ {(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 8936e0b5b..6e340d065 100644 --- a/app/components/markdown.tsx +++ b/app/components/markdown.tsx @@ -21,6 +21,7 @@ import { } from "./artifacts"; import { useChatStore } from "../store"; import { IconButton } from "./button"; + import { useAppConfig } from "../store/config"; export function Mermaid(props: { code: string }) { diff --git a/app/components/model-config.module.scss b/app/components/model-config.module.scss new file mode 100644 index 000000000..40ba03f86 --- /dev/null +++ b/app/components/model-config.module.scss @@ -0,0 +1,7 @@ +.select-compress-model { + width: 60%; + select { + max-width: 100%; + white-space: normal; + } +} diff --git a/app/components/model-config.tsx b/app/components/model-config.tsx index 04cd3ff01..f2297e10b 100644 --- a/app/components/model-config.tsx +++ b/app/components/model-config.tsx @@ -6,6 +6,7 @@ import { InputRange } from "./input-range"; import { ListItem, Select } from "./ui-lib"; import { useAllModels } from "../utils/hooks"; import { groupBy } from "lodash-es"; +import styles from "./model-config.module.scss"; export function ModelConfigList(props: { modelConfig: ModelConfig; @@ -242,6 +243,7 @@ export function ModelConfigList(props: { subTitle={Locale.Settings.CompressModel.SubTitle} >