Merge branch 'main' of github.com:sijinhui/ChatGPT-Next-Web

This commit is contained in:
sijinhui 2024-10-01 09:17:58 +08:00
commit c7f8930215
14 changed files with 102 additions and 147 deletions

View File

@ -18,11 +18,11 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with GPT3, GPT4
[![MacOS][MacOS-image]][download-url] [![MacOS][MacOS-image]][download-url]
[![Linux][Linux-image]][download-url] [![Linux][Linux-image]][download-url]
[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) [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)
[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) [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-url]: https://nextchat.dev/chat?utm_source=readme
[saas-image]: https://img.shields.io/badge/NextChat-Saas-green?logo=microsoftedge [saas-image]: https://img.shields.io/badge/NextChat-Saas-green?logo=microsoftedge
[web-url]: https://app.nextchat.dev/ [web-url]: https://app.nextchat.dev/
[download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases [download-url]: https://github.com/Yidadaa/ChatGPT-Next-Web/releases

View File

@ -8,7 +8,7 @@
一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。 一键免费部署你的私人 ChatGPT 网页应用,支持 GPT3, GPT4 & Gemini Pro 模型。
[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) [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)
[<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](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) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [<img src="https://vercel.com/button" alt="Deploy on Zeabur" height="30">](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) [<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)

View File

@ -5,7 +5,7 @@
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。 ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
[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) [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)
[<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](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) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web) [<img src="https://vercel.com/button" alt="Zeaburでデプロイ" height="30">](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) [<img src="https://zeabur.com/button.svg" alt="Zeaburでデプロイ" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://gitpod.io/button/open-in-gitpod.svg" alt="Gitpodで開く" height="30">](https://gitpod.io/#https://github.com/Yidadaa/ChatGPT-Next-Web)

View File

@ -35,7 +35,6 @@ export default function SetPasswordPage() {
const onFinish: FormProps<FieldType>["onFinish"] = (values) => { const onFinish: FormProps<FieldType>["onFinish"] = (values) => {
// setLoading(true); // setLoading(true);
// console.log('-------------', values) // console.log('-------------', values)
// @ts-expect-error
fetch(`/api/user/${session?.user?.id}`, { fetch(`/api/user/${session?.user?.id}`, {
method: "PUT", method: "PUT",
credentials: "include", credentials: "include",
@ -61,31 +60,28 @@ export default function SetPasswordPage() {
layout="vertical" layout="vertical"
onFinish={onFinish} onFinish={onFinish}
> >
{ {status === "authenticated" && session?.user?.hasPassword && (
// @ts-expect-error <Form.Item
status === "authenticated" && session?.user?.hasPassword && ( name="user[old_password]"
<Form.Item label="Old password"
name="user[old_password]" rules={[
label="Old password" {
rules={[ validator: async (_, value) => {
{ if (!value) {
validator: async (_, value) => { return Promise.reject(new Error("请填写该字段"));
if (!value) { }
return Promise.reject(new Error("请填写该字段"));
}
},
}, },
]} },
> ]}
<Input >
prefix={<LockOutlined className="site-form-item-icon" />} <Input
type="password" prefix={<LockOutlined className="site-form-item-icon" />}
autoComplete="current-password" type="password"
id="user_old_password" autoComplete="current-password"
/> id="user_old_password"
</Form.Item> />
) </Form.Item>
} )}
<Form.Item <Form.Item
name="user[password]" name="user[password]"

View File

@ -127,7 +127,6 @@ export default function UserLoginCore() {
// 手动获取一遍session // 手动获取一遍session
getSession() getSession()
.then((value) => { .then((value) => {
// @ts-expect-error
if (!value?.user?.hasPassword) { if (!value?.user?.hasPassword) {
if (result_url === "/") { if (result_url === "/") {
result_url = "/login/set-password"; result_url = "/login/set-password";

View File

@ -438,14 +438,23 @@ function useScrollToBottom(
if (dom) { if (dom) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
setAutoScroll(true); setAutoScroll(true);
dom.scrollTo(0, dom.scrollHeight); // dom.scrollTo(0, dom.scrollHeight);
// 丝滑一点
dom.scrollTo({
top: dom.scrollHeight,
behavior: "smooth",
});
}); });
} }
} }
// auto scroll // auto scroll
useEffect(() => { useEffect(() => {
if (autoScroll && !detach) { // if (autoScroll && !detach) {
// scrollDomToBottom();
// }
// 自动滚动一直有bug直接强制修改了
if (autoScroll) {
scrollDomToBottom(); scrollDomToBottom();
} }
}); });

View File

@ -253,7 +253,7 @@ function tryWrapHtmlCode(text: string) {
}, },
) )
.replace( .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) => { (match, bodyEnd, space, htmlEnd, newLine, quoteEnd) => {
return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match; return !quoteEnd ? bodyEnd + space + htmlEnd + "\n```\n" : match;
}, },

View File

@ -1,88 +0,0 @@
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# 匹配以 www. 开头的请求并重定向到 HTTPS 的不带 www.
server {
listen 80;
listen 443 ssl;
server_name ~^www\.(?<domain>.+)$;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate "/root/cert.pem";
ssl_certificate_key "/root/cert.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000";
return 301 https://$domain$request_uri;
}
# 匹配任意 HTTP 重定向到 HTTPS
server {
listen 80 default_server;
server_name _;
# 将 HTTP 请求重定向到 HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2 default_server;
server_name _;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_certificate "/root/cert.pem";
ssl_certificate_key "/root/cert.key";
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 10m;
ssl_ciphers PROFILE=SYSTEM;
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000";
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
#REWRITE-START
if ($host ~* ^www\.(.+)$) {
set $new_host \$1;
return 301 https://$new_host$request_uri;
}
#REWRITE-END
#PROXY-START/
location ^~ /
{
proxy_pass http://127.0.0.1:23000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1.1;
# 禁止缓存的相关设置
proxy_buffering off;
proxy_no_cache 1;
proxy_cache_bypass 1;
add_header X-Accel-Buffering "no";
# 设置代理超时
proxy_read_timeout 24h;
proxy_connect_timeout 3m;
proxy_send_timeout 24h;
proxy_max_temp_file_size 0;
}
#PROXY-END/
}

View File

@ -9,6 +9,9 @@ import { isEmail, isName } from "@/lib/auth_list";
import {createTransport} from "nodemailer"; import {createTransport} from "nodemailer";
import { comparePassword } from "@/lib/utils"; import { comparePassword } from "@/lib/utils";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import { type Session } from "next-auth";
import { type JWT } from "next-auth/jwt";
const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES; const SECURE_COOKIES:boolean = !!process.env.SECURE_COOKIES;
let verificationTokens = new Map(); let verificationTokens = new Map();
@ -153,25 +156,23 @@ export const authOptions: NextAuthOptions = {
if (user) { if (user) {
token.user = user; token.user = user;
} else { } else {
const updateUser = await prisma.user.findUnique({ where: { id: token.sub }}); const updateUser: User | null = await prisma.user.findUnique({ where: { id: token.sub }});
// console.log('========', updateUser)
if (!updateUser || !updateUser.allowToLogin) { if (!updateUser || !updateUser.allowToLogin) {
throw new Error('无法刷新令牌,用户状态不正确'); throw new Error('无法刷新令牌,用户状态不正确');
} }
token.user = updateUser; token.user = updateUser as User;
} }
return token; return token;
}, },
session: async ({ session, token }) => { session: async ({ session, token }: {
session: Session,
token: JWT
}) => {
session.user = { session.user = {
...session.user, ...session.user,
// @ts-expect-error id: token?.sub ?? "",
id: token?.sub,
// @ts-expect-error
username: token?.user?.username || token?.user?.gh_username, username: token?.user?.username || token?.user?.gh_username,
// @ts-expect-error
hasPassword: !!token?.user?.password, hasPassword: !!token?.user?.password,
// @ts-expect-error
isAdmin: token?.user?.isAdmin, isAdmin: token?.user?.isAdmin,
}; };
// console.log('555555555,', session, token) // console.log('555555555,', session, token)

View File

@ -1,21 +1,21 @@
import { isName } from "@/lib/auth_list"; import { isName } from "@/lib/auth_list";
import { CUS_JWT } from "@/lib/auth_type"; import { type JWT } from "next-auth/jwt";
export async function VerifiedUser(session: CUS_JWT | null) { export async function VerifiedUser(session: JWT | null) {
const userId = session?.sub const userId = session?.sub
const name = session?.email || session?.name const name = session?.email || session?.name
return !!(name && isName(name) && userId); return !!(name && isName(name) && userId);
} }
export async function VerifiedAdminUser(session: CUS_JWT | null) { export async function VerifiedAdminUser(session: JWT | null) {
// console.log('-------', session, session?.user?.isAdmin) // console.log('-------', session, session?.user?.isAdmin)
return !!session?.user?.isAdmin; return !!session?.user?.isAdmin;
// const name = session?.email || session?.name // const name = session?.email || session?.name
// return !!(name && ADMIN_LIST.includes(name)); // return !!(name && ADMIN_LIST.includes(name));
} }
export function VerifiedNeedSetPassword(path: string, session: CUS_JWT | null,) { export function VerifiedNeedSetPassword(path: string, session: JWT | null,) {
const need_set_pwd = !session?.user?.password const need_set_pwd = !session?.user?.password
return path === "/login/set-password" && need_set_pwd; return path === "/login/set-password" && need_set_pwd;
} }

View File

@ -1,6 +0,0 @@
import { JWT } from "next-auth/jwt";
import { User } from "@prisma/client";
export type CUS_JWT = JWT & {
user: User,
}

45
lib/types/next-auth.d.ts vendored Normal file
View File

@ -0,0 +1,45 @@
// types/next-auth.d.ts
import { DefaultSession, DefaultUser } from "next-auth";
declare module "next-auth" {
/**
* Session
*/
interface Session {
user: {
id: string;
username?: string | null;
hasPassword?: boolean | null;
isAdmin?: boolean | null;
} & DefaultSession["user"];
}
/**
* User
* AdapterUser
*/
interface User extends DefaultUser {
id: string;
username?: string;
gh_username?: string;
password?: string;
isAdmin?: boolean;
}
}
declare module "next-auth/jwt" {
/**
* JWT
*/
interface JWT {
user?: {
id: string;
username?: string | null;
gh_username?: string | null;
password?: string | null;
isAdmin?: boolean | null;
};
}
}

View File

@ -1,9 +1,7 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt"; import { getToken } from "next-auth/jwt";
import { VerifiedUser, VerifiedAdminUser, VerifiedNeedSetPassword } from "@/lib/auth_client"; import { VerifiedUser, VerifiedAdminUser } from "@/lib/auth_client";
import { CUS_JWT } from "@/lib/auth_type";
export default async function middleware(req: NextRequest) { export default async function middleware(req: NextRequest) {
const url = req.nextUrl; const url = req.nextUrl;
@ -17,8 +15,8 @@ export default async function middleware(req: NextRequest) {
} }
const session = await getToken({ req }); const session = await getToken({ req });
const isUser = await VerifiedUser(session as CUS_JWT); const isUser = await VerifiedUser(session);
const isAdminUser = await VerifiedAdminUser(session as CUS_JWT); const isAdminUser = await VerifiedAdminUser(session);
// console.log('----session', session, '---isUser', isUser, '---isAdmin', isAdminUser) // console.log('----session', session, '---isUser', isUser, '---isAdmin', isAdminUser)
// 管理员页面的api接口还是要认证的 // 管理员页面的api接口还是要认证的
if (path.startsWith('/api/admin/')) { if (path.startsWith('/api/admin/')) {
@ -45,7 +43,7 @@ export default async function middleware(req: NextRequest) {
); );
} }
// if (VerifiedNeedSetPassword(path, session as CUS_JWT)) { // if (VerifiedNeedSetPassword(path, session)) {
// console.log('-0-0-- 需要修改密码', ) // console.log('-0-0-- 需要修改密码', )
// // return NextResponse.redirect(new URL("/login/set-password", req.url)) // // return NextResponse.redirect(new URL("/login/set-password", req.url))
// } // }

View File

@ -21,7 +21,8 @@
], ],
"paths": { "paths": {
"@/*": ["./*"] "@/*": ["./*"]
} },
"typeRoots": ["lib/types"]
}, },
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"], "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "app/calcTextareaHeight.ts"],
"exclude": ["node_modules"] "exclude": ["node_modules"]