Skip to content

Commit 814fd27

Browse files
authored
Merge pull request #6542 from JI4JUN/feat/support-302ai-provider
Feat/support 302ai provider
2 parents 673f907 + 29dbffa commit 814fd27

File tree

33 files changed

+794
-7
lines changed

33 files changed

+794
-7
lines changed

.env.template

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,9 @@ SILICONFLOW_API_KEY=
8181

8282
### siliconflow Api url (optional)
8383
SILICONFLOW_URL=
84+
85+
### 302.AI Api key (optional)
86+
AI302_API_KEY=
87+
88+
### 302.AI Api url (optional)
89+
AI302_URL=

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77

88

9-
109
<h1 align="center">NextChat</h1>
1110

1211
English / [简体中文](./README_CN.md)
@@ -36,10 +35,19 @@ English / [简体中文](./README_CN.md)
3635

3736
[<img src="https://zeabur.com/button.svg" alt="Deploy on Zeabur" height="30">](https://zeabur.com/templates/ZBUEFA) [<img src="https://vercel.com/button" alt="Deploy on Vercel" 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://gitpod.io/button/open-in-gitpod.svg" alt="Open in Gitpod" height="30">](https://gitpod.io/#https://github.com/ChatGPTNextWeb/NextChat)
3837

38+
3939
[<img src="https://github.com/user-attachments/assets/903482d4-3e87-4134-9af1-f2588fa90659" height="50" width="" >](https://monica.im/?utm=nxcrp)
4040

4141
</div>
4242

43+
## ❤️ Sponsor AI API
44+
<a href='https://302.ai/'>
45+
<img src="https://github.com/user-attachments/assets/a03edf82-2031-4f23-bdb8-bfc0bfd168a4" width="100%" alt="icon"/>
46+
</a>
47+
48+
[302.AI](https://302.ai/) is a pay-as-you-go AI application platform that offers the most comprehensive AI APIs and online applications available.
49+
50+
4351
## 🥳 Cheer for NextChat iOS Version Online!
4452
> [👉 Click Here to Install Now](https://apps.apple.com/us/app/nextchat-ai/id6743085599)
4553

README_CN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
<img src="./docs/images/ent.svg" alt="icon"/>
55
</a>
66

7+
78
<h1 align="center">NextChat</h1>
89

910
一键免费部署你的私人 ChatGPT 网页应用,支持 Claude, GPT4 & Gemini Pro 模型。
@@ -14,6 +15,14 @@
1415

1516
</div>
1617

18+
## Sponsor AI API
19+
<a href='https://302.ai/'>
20+
<img src="https://github.com/user-attachments/assets/d8c0c513-1e18-4d3b-a2a9-ff3696aec0d4" width="100%" alt="icon"/>
21+
</a>
22+
23+
[302.AI](https://302.ai/) 是一个按需付费的AI应用平台,提供市面上最全的AI API和AI在线应用。
24+
25+
1726
## 企业版
1827

1928
满足您公司私有化部署和定制需求

README_JA.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div align="center">
22
<img src="./docs/images/ent.svg" alt="プレビュー"/>
33

4+
45
<h1 align="center">NextChat</h1>
56

67
ワンクリックで無料であなた専用の ChatGPT ウェブアプリをデプロイ。GPT3、GPT4 & Gemini Pro モデルをサポート。
@@ -12,6 +13,14 @@
1213

1314
</div>
1415

16+
## Sponsor AI API
17+
<a href='https://302.ai/'>
18+
<img src="https://github.com/user-attachments/assets/6cf24233-1010-43e0-9a83-a11159866175" width="100%" alt="icon"/>
19+
</a>
20+
21+
[302.AI](https://302.ai/) は、オンデマンドで支払うAIアプリケーションプラットフォームで、最も安全なAI APIとAIオンラインアプリケーションを提供します。
22+
23+
1524
## 企業版
1625

1726
あなたの会社のプライベートデプロイとカスタマイズのニーズに応える

app/api/302ai.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+
AI302_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("[302.AI 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["302.AI"]);
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("[302.AI] ", 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["302.AI"], "");
46+
47+
let baseUrl = serverConfig.ai302Url || AI302_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["302.AI"] 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(`[302.AI] 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+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { handle as siliconflowHandler } from "../../siliconflow";
1515
import { handle as xaiHandler } from "../../xai";
1616
import { handle as chatglmHandler } from "../../glm";
1717
import { handle as proxyHandler } from "../../proxy";
18+
import { handle as ai302Handler } from "../../302ai";
1819

1920
async function handle(
2021
req: NextRequest,
@@ -52,6 +53,8 @@ async function handle(
5253
return siliconflowHandler(req, { params });
5354
case ApiPath.OpenAI:
5455
return openaiHandler(req, { params });
56+
case ApiPath["302.AI"]:
57+
return ai302Handler(req, { params });
5558
default:
5659
return proxyHandler(req, { params });
5760
}

app/client/api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { DeepSeekApi } from "./platforms/deepseek";
2424
import { XAIApi } from "./platforms/xai";
2525
import { ChatGLMApi } from "./platforms/glm";
2626
import { SiliconflowApi } from "./platforms/siliconflow";
27+
import { Ai302Api } from "./platforms/ai302";
2728

2829
export const ROLES = ["system", "user", "assistant"] as const;
2930
export type MessageRole = (typeof ROLES)[number];
@@ -173,6 +174,9 @@ export class ClientApi {
173174
case ModelProvider.SiliconFlow:
174175
this.llm = new SiliconflowApi();
175176
break;
177+
case ModelProvider["302.AI"]:
178+
this.llm = new Ai302Api();
179+
break;
176180
default:
177181
this.llm = new ChatGPTApi();
178182
}
@@ -265,6 +269,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
265269
const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM;
266270
const isSiliconFlow =
267271
modelConfig.providerName === ServiceProvider.SiliconFlow;
272+
const isAI302 = modelConfig.providerName === ServiceProvider["302.AI"];
268273
const isEnabledAccessControl = accessStore.enabledAccessControl();
269274
const apiKey = isGoogle
270275
? accessStore.googleApiKey
@@ -290,6 +295,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
290295
? accessStore.iflytekApiKey && accessStore.iflytekApiSecret
291296
? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret
292297
: ""
298+
: isAI302
299+
? accessStore.ai302ApiKey
293300
: accessStore.openaiApiKey;
294301
return {
295302
isGoogle,
@@ -304,6 +311,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
304311
isXAI,
305312
isChatGLM,
306313
isSiliconFlow,
314+
isAI302,
307315
apiKey,
308316
isEnabledAccessControl,
309317
};
@@ -332,6 +340,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
332340
isXAI,
333341
isChatGLM,
334342
isSiliconFlow,
343+
isAI302,
335344
apiKey,
336345
isEnabledAccessControl,
337346
} = getConfig();
@@ -382,6 +391,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
382391
return new ClientApi(ModelProvider.ChatGLM);
383392
case ServiceProvider.SiliconFlow:
384393
return new ClientApi(ModelProvider.SiliconFlow);
394+
case ServiceProvider["302.AI"]:
395+
return new ClientApi(ModelProvider["302.AI"]);
385396
default:
386397
return new ClientApi(ModelProvider.GPT);
387398
}

0 commit comments

Comments
 (0)