Skip to content

Commit b0f78e9

Browse files
authored
Merge branch 'main' into main
2 parents 29b9a20 + d56566c commit b0f78e9

31 files changed

Lines changed: 776 additions & 89 deletions

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ One-Click to get a well-designed cross-platform ChatGPT web UI, with Claude, GPT
1919
[![MacOS][MacOS-image]][download-url]
2020
[![Linux][Linux-image]][download-url]
2121

22-
[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)
22+
[NextChatAI](https://nextchat.dev/chat?utm_source=readme) / [Web App Demo](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)
2323

24-
[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)
24+
[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)
2525

2626
[saas-url]: https://nextchat.dev/chat?utm_source=readme
2727
[saas-image]: https://img.shields.io/badge/NextChat-Saas-green?logo=microsoftedge
@@ -312,6 +312,14 @@ ChatGLM Api Key.
312312

313313
ChatGLM Api Url.
314314

315+
### `DEEPSEEK_API_KEY` (optional)
316+
317+
DeepSeek Api Key.
318+
319+
### `DEEPSEEK_URL` (optional)
320+
321+
DeepSeek Api Url.
322+
315323
### `HIDE_USER_API_KEY` (optional)
316324

317325
> Default: Empty

README_CN.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,14 @@ ChatGLM Api Key.
192192

193193
ChatGLM Api Url.
194194

195+
### `DEEPSEEK_API_KEY` (可选)
196+
197+
DeepSeek Api Key.
198+
199+
### `DEEPSEEK_URL` (可选)
200+
201+
DeepSeek Api Url.
202+
195203

196204
### `HIDE_USER_API_KEY` (可选)
197205

app/api/[provider]/[...path]/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { handle as alibabaHandler } from "../../alibaba";
1111
import { handle as moonshotHandler } from "../../moonshot";
1212
import { handle as stabilityHandler } from "../../stability";
1313
import { handle as iflytekHandler } from "../../iflytek";
14+
import { handle as deepseekHandler } from "../../deepseek";
1415
import { handle as xaiHandler } from "../../xai";
1516
import { handle as chatglmHandler } from "../../glm";
1617
import { handle as proxyHandler } from "../../proxy";
@@ -44,6 +45,8 @@ async function handle(
4445
return stabilityHandler(req, { params });
4546
case ApiPath.Iflytek:
4647
return iflytekHandler(req, { params });
48+
case ApiPath.DeepSeek:
49+
return deepseekHandler(req, { params });
4750
case ApiPath.XAI:
4851
return xaiHandler(req, { params });
4952
case ApiPath.ChatGLM:

app/api/alibaba.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { prettyObject } from "@/app/utils/format";
99
import { NextRequest, NextResponse } from "next/server";
1010
import { auth } from "@/app/api/auth";
11-
import { isModelAvailableInServer } from "@/app/utils/model";
11+
import { isModelNotavailableInServer } from "@/app/utils/model";
1212

1313
const serverConfig = getServerSideConfig();
1414

@@ -89,7 +89,7 @@ async function request(req: NextRequest) {
8989

9090
// not undefined and is false
9191
if (
92-
isModelAvailableInServer(
92+
isModelNotavailableInServer(
9393
serverConfig.customModels,
9494
jsonBody?.model as string,
9595
ServiceProvider.Alibaba as string,

app/api/anthropic.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { prettyObject } from "@/app/utils/format";
1010
import { NextRequest, NextResponse } from "next/server";
1111
import { auth } from "./auth";
12-
import { isModelAvailableInServer } from "@/app/utils/model";
12+
import { isModelNotavailableInServer } from "@/app/utils/model";
1313
import { cloudflareAIGatewayUrl } from "@/app/utils/cloudflare";
1414

1515
const ALLOWD_PATH = new Set([Anthropic.ChatPath, Anthropic.ChatPath1]);
@@ -122,7 +122,7 @@ async function request(req: NextRequest) {
122122

123123
// not undefined and is false
124124
if (
125-
isModelAvailableInServer(
125+
isModelNotavailableInServer(
126126
serverConfig.customModels,
127127
jsonBody?.model as string,
128128
ServiceProvider.Anthropic as string,

app/api/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
9191
systemApiKey =
9292
serverConfig.iflytekApiKey + ":" + serverConfig.iflytekApiSecret;
9393
break;
94+
case ModelProvider.DeepSeek:
95+
systemApiKey = serverConfig.deepseekApiKey;
96+
break;
9497
case ModelProvider.XAI:
9598
systemApiKey = serverConfig.xaiApiKey;
9699
break;

app/api/baidu.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { prettyObject } from "@/app/utils/format";
99
import { NextRequest, NextResponse } from "next/server";
1010
import { auth } from "@/app/api/auth";
11-
import { isModelAvailableInServer } from "@/app/utils/model";
11+
import { isModelNotavailableInServer } from "@/app/utils/model";
1212
import { getAccessToken } from "@/app/utils/baidu";
1313

1414
const serverConfig = getServerSideConfig();
@@ -104,7 +104,7 @@ async function request(req: NextRequest) {
104104

105105
// not undefined and is false
106106
if (
107-
isModelAvailableInServer(
107+
isModelNotavailableInServer(
108108
serverConfig.customModels,
109109
jsonBody?.model as string,
110110
ServiceProvider.Baidu as string,

app/api/bytedance.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import { prettyObject } from "@/app/utils/format";
99
import { NextRequest, NextResponse } from "next/server";
1010
import { auth } from "@/app/api/auth";
11-
import { isModelAvailableInServer } from "@/app/utils/model";
11+
import { isModelNotavailableInServer } from "@/app/utils/model";
1212

1313
const serverConfig = getServerSideConfig();
1414

@@ -88,7 +88,7 @@ async function request(req: NextRequest) {
8888

8989
// not undefined and is false
9090
if (
91-
isModelAvailableInServer(
91+
isModelNotavailableInServer(
9292
serverConfig.customModels,
9393
jsonBody?.model as string,
9494
ServiceProvider.ByteDance as string,

app/api/common.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { NextRequest, NextResponse } from "next/server";
22
import { getServerSideConfig } from "../config/server";
33
import { OPENAI_BASE_URL, ServiceProvider } from "../constant";
44
import { cloudflareAIGatewayUrl } from "../utils/cloudflare";
5-
import { getModelProvider, isModelAvailableInServer } from "../utils/model";
5+
import { getModelProvider, isModelNotavailableInServer } from "../utils/model";
66

77
const serverConfig = getServerSideConfig();
88

@@ -118,15 +118,14 @@ export async function requestOpenai(req: NextRequest) {
118118

119119
// not undefined and is false
120120
if (
121-
isModelAvailableInServer(
121+
isModelNotavailableInServer(
122122
serverConfig.customModels,
123123
jsonBody?.model as string,
124-
ServiceProvider.OpenAI as string,
125-
) ||
126-
isModelAvailableInServer(
127-
serverConfig.customModels,
128-
jsonBody?.model as string,
129-
ServiceProvider.Azure as string,
124+
[
125+
ServiceProvider.OpenAI,
126+
ServiceProvider.Azure,
127+
jsonBody?.model as string, // support provider-unspecified model
128+
],
130129
)
131130
) {
132131
return NextResponse.json(

app/api/deepseek.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { getServerSideConfig } from "@/app/config/server";
2+
import {
3+
DEEPSEEK_BASE_URL,
4+
ApiPath,
5+
ModelProvider,
6+
ServiceProvider,
7+
} from "@/app/constant";
8+
import { prettyObject } from "@/app/utils/format";
9+
import { NextRequest, NextResponse } from "next/server";
10+
import { auth } from "@/app/api/auth";
11+
import { isModelNotavailableInServer } from "@/app/utils/model";
12+
13+
const serverConfig = getServerSideConfig();
14+
15+
export async function handle(
16+
req: NextRequest,
17+
{ params }: { params: { path: string[] } },
18+
) {
19+
console.log("[DeepSeek Route] params ", params);
20+
21+
if (req.method === "OPTIONS") {
22+
return NextResponse.json({ body: "OK" }, { status: 200 });
23+
}
24+
25+
const authResult = auth(req, ModelProvider.DeepSeek);
26+
if (authResult.error) {
27+
return NextResponse.json(authResult, {
28+
status: 401,
29+
});
30+
}
31+
32+
try {
33+
const response = await request(req);
34+
return response;
35+
} catch (e) {
36+
console.error("[DeepSeek] ", e);
37+
return NextResponse.json(prettyObject(e));
38+
}
39+
}
40+
41+
async function request(req: NextRequest) {
42+
const controller = new AbortController();
43+
44+
// alibaba use base url or just remove the path
45+
let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.DeepSeek, "");
46+
47+
let baseUrl = serverConfig.deepseekUrl || DEEPSEEK_BASE_URL;
48+
49+
if (!baseUrl.startsWith("http")) {
50+
baseUrl = `https://${baseUrl}`;
51+
}
52+
53+
if (baseUrl.endsWith("/")) {
54+
baseUrl = baseUrl.slice(0, -1);
55+
}
56+
57+
console.log("[Proxy] ", path);
58+
console.log("[Base Url]", baseUrl);
59+
60+
const timeoutId = setTimeout(
61+
() => {
62+
controller.abort();
63+
},
64+
10 * 60 * 1000,
65+
);
66+
67+
const fetchUrl = `${baseUrl}${path}`;
68+
const fetchOptions: RequestInit = {
69+
headers: {
70+
"Content-Type": "application/json",
71+
Authorization: req.headers.get("Authorization") ?? "",
72+
},
73+
method: req.method,
74+
body: req.body,
75+
redirect: "manual",
76+
// @ts-ignore
77+
duplex: "half",
78+
signal: controller.signal,
79+
};
80+
81+
// #1815 try to refuse some request to some models
82+
if (serverConfig.customModels && req.body) {
83+
try {
84+
const clonedBody = await req.text();
85+
fetchOptions.body = clonedBody;
86+
87+
const jsonBody = JSON.parse(clonedBody) as { model?: string };
88+
89+
// not undefined and is false
90+
if (
91+
isModelNotavailableInServer(
92+
serverConfig.customModels,
93+
jsonBody?.model as string,
94+
ServiceProvider.Moonshot as string,
95+
)
96+
) {
97+
return NextResponse.json(
98+
{
99+
error: true,
100+
message: `you are not allowed to use ${jsonBody?.model} model`,
101+
},
102+
{
103+
status: 403,
104+
},
105+
);
106+
}
107+
} catch (e) {
108+
console.error(`[DeepSeek] filter`, e);
109+
}
110+
}
111+
try {
112+
const res = await fetch(fetchUrl, fetchOptions);
113+
114+
// to prevent browser prompt for credentials
115+
const newHeaders = new Headers(res.headers);
116+
newHeaders.delete("www-authenticate");
117+
// to disable nginx buffering
118+
newHeaders.set("X-Accel-Buffering", "no");
119+
120+
return new Response(res.body, {
121+
status: res.status,
122+
statusText: res.statusText,
123+
headers: newHeaders,
124+
});
125+
} finally {
126+
clearTimeout(timeoutId);
127+
}
128+
}

0 commit comments

Comments
 (0)