Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ AI302_API_KEY=

### 302.AI Api url (optional)
AI302_URL=

### Baichuan Api key (optional)
BAICHUAN_API_KEY=

### Baichuan Api url (optional)
BAICHUAN_URL=
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,14 @@ DeepSeek Api Key.

DeepSeek Api Url.

### `BAICHUAN_API_KEY` (optional)

Baichuan AI Api Key.

### `BAICHUAN_URL` (optional)

Baichuan Api Url, default: `https://api.baichuan-ai.com`.

### `HIDE_USER_API_KEY` (optional)

> Default: Empty
Expand Down
8 changes: 8 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,14 @@ DeepSeek Api Key.

DeepSeek Api Url.

### `BAICHUAN_API_KEY` (可选)

Baichuan(百川智能)Api Key.

### `BAICHUAN_URL` (可选)

Baichuan Api Url,默认为 `https://api.baichuan-ai.com`。

### `HIDE_USER_API_KEY` (可选)

如果你不想让用户自行填入 API Key,将此环境变量设置为 1 即可。
Expand Down
8 changes: 8 additions & 0 deletions README_JA.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,14 @@ ByteDance API の URL。

アリババ(千问)API の URL。

### `BAICHUAN_API_KEY` (オプション)

Baichuan AI API キー。

### `BAICHUAN_URL` (オプション)

Baichuan API の URL。デフォルト: `https://api.baichuan-ai.com`。

### `HIDE_USER_API_KEY` (オプション)

ユーザーが API キーを入力できないようにしたい場合は、この環境変数を 1 に設定します。
Expand Down
8 changes: 8 additions & 0 deletions README_KO.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ DeepSeek API 키입니다.

DeepSeek API URL입니다.

### `BAICHUAN_API_KEY` (선택 사항)

Baichuan AI API 키입니다.

### `BAICHUAN_URL` (선택 사항)

Baichuan API URL입니다. 기본값: `https://api.baichuan-ai.com`.

### `HIDE_USER_API_KEY` (선택 사항)

> 기본값: 비어 있음
Expand Down
3 changes: 3 additions & 0 deletions app/api/[provider]/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { handle as xaiHandler } from "../../xai";
import { handle as chatglmHandler } from "../../glm";
import { handle as proxyHandler } from "../../proxy";
import { handle as ai302Handler } from "../../302ai";
import { handle as baichuanHandler } from "../../baichuan";

async function handle(
req: NextRequest,
Expand Down Expand Up @@ -55,6 +56,8 @@ async function handle(
return openaiHandler(req, { params });
case ApiPath["302.AI"]:
return ai302Handler(req, { params });
case ApiPath.Baichuan:
return baichuanHandler(req, { params });
default:
return proxyHandler(req, { params });
}
Expand Down
3 changes: 3 additions & 0 deletions app/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.DeepSeek:
systemApiKey = serverConfig.deepseekApiKey;
break;
case ModelProvider.Baichuan:
systemApiKey = serverConfig.baichuanApiKey;
break;
case ModelProvider.XAI:
systemApiKey = serverConfig.xaiApiKey;
break;
Expand Down
187 changes: 187 additions & 0 deletions app/api/baichuan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { getServerSideConfig } from "@/app/config/server";
import {
BAICHUAN_BASE_URL,
ApiPath,
ModelProvider,
ServiceProvider,
} from "@/app/constant";
import { prettyObject } from "@/app/utils/format";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/api/auth";
import { isModelNotavailableInServer } from "@/app/utils/model";

const serverConfig = getServerSideConfig();

export async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
console.log("[Baichuan Route] params ", params);

if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}

const authResult = auth(req, ModelProvider.Baichuan);
if (authResult.error) {
return NextResponse.json(authResult, {
status: 401,
});
}

try {
const response = await request(req);
return response;
} catch (e) {
console.error("[Baichuan] ", e);
return NextResponse.json(prettyObject(e));
}
}

async function request(req: NextRequest) {
const controller = new AbortController();

let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Baichuan, "");

let baseUrl = serverConfig.baichuanUrl || BAICHUAN_BASE_URL;

if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}

if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}

console.log("[Proxy] ", path);
console.log("[Base Url]", baseUrl);

const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);

const fetchUrl = `${baseUrl}${path}`;
const fetchOptions: RequestInit = {
headers: {
"Content-Type": "application/json",
Authorization: req.headers.get("Authorization") ?? "",
},
method: req.method,
body: req.body,
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};

// #1815 try to refuse some request to some models
if (serverConfig.customModels && req.body) {
try {
const clonedBody = await req.text();
fetchOptions.body = clonedBody;

const jsonBody = JSON.parse(clonedBody) as { model?: string };

// not undefined and is false
if (
isModelNotavailableInServer(
serverConfig.customModels,
jsonBody?.model as string,
ServiceProvider.Baichuan as string,
)
) {
return NextResponse.json(
{
error: true,
message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
},
);
}
} catch (e) {
console.error(`[Baichuan] filter`, e);
}
}
try {
const res = await fetch(fetchUrl, fetchOptions);

// 检查响应状态
if (!res.ok) {
let errorMessage = `Baichuan API Error: ${res.status} ${res.statusText}`;
try {
const errorBody = await res.text();
if (errorBody) {
const errorJson = JSON.parse(errorBody);
errorMessage = errorJson.error?.message || errorJson.message || errorMessage;
}
} catch (e) {
console.error("[Baichuan] Failed to parse error response:", e);
}

return NextResponse.json(
{
error: true,
message: errorMessage,
},
{
status: res.status,
},
);
}

// 检查响应体是否为空
if (!res.body) {
console.error("[Baichuan] Empty response body from server");
return NextResponse.json(
{
error: true,
message: "Empty response from Baichuan API",
},
{
status: 500,
},
);
}

// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete("www-authenticate");
// to disable nginx buffering
newHeaders.set("X-Accel-Buffering", "no");

return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: newHeaders,
});
} catch (fetchError: any) {
console.error("[Baichuan] Fetch error:", fetchError);
clearTimeout(timeoutId);

// 处理各种网络错误
let errorMessage = "Network error";
if (fetchError.name === "AbortError") {
errorMessage = "Request timeout";
} else if (fetchError.message) {
errorMessage = fetchError.message;
}

return NextResponse.json(
{
error: true,
message: `Baichuan API request failed: ${errorMessage}`,
},
{
status: 500,
},
);
} finally {
clearTimeout(timeoutId);
}
}

11 changes: 11 additions & 0 deletions app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { XAIApi } from "./platforms/xai";
import { ChatGLMApi } from "./platforms/glm";
import { SiliconflowApi } from "./platforms/siliconflow";
import { Ai302Api } from "./platforms/ai302";
import { BaichuanApi } from "./platforms/baichuan";

export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
Expand Down Expand Up @@ -177,6 +178,9 @@ export class ClientApi {
case ModelProvider["302.AI"]:
this.llm = new Ai302Api();
break;
case ModelProvider.Baichuan:
this.llm = new BaichuanApi();
break;
default:
this.llm = new ChatGPTApi();
}
Expand Down Expand Up @@ -270,6 +274,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
const isSiliconFlow =
modelConfig.providerName === ServiceProvider.SiliconFlow;
const isAI302 = modelConfig.providerName === ServiceProvider["302.AI"];
const isBaichuan = modelConfig.providerName === ServiceProvider.Baichuan;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
Expand Down Expand Up @@ -297,6 +302,8 @@ export function getHeaders(ignoreHeaders: boolean = false) {
: ""
: isAI302
? accessStore.ai302ApiKey
: isBaichuan
? accessStore.baichuanApiKey
: accessStore.openaiApiKey;
return {
isGoogle,
Expand All @@ -312,6 +319,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isChatGLM,
isSiliconFlow,
isAI302,
isBaichuan,
apiKey,
isEnabledAccessControl,
};
Expand Down Expand Up @@ -341,6 +349,7 @@ export function getHeaders(ignoreHeaders: boolean = false) {
isChatGLM,
isSiliconFlow,
isAI302,
isBaichuan,
apiKey,
isEnabledAccessControl,
} = getConfig();
Expand Down Expand Up @@ -393,6 +402,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.SiliconFlow);
case ServiceProvider["302.AI"]:
return new ClientApi(ModelProvider["302.AI"]);
case ServiceProvider.Baichuan:
return new ClientApi(ModelProvider.Baichuan);
default:
return new ClientApi(ModelProvider.GPT);
}
Expand Down
Loading