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
10 changes: 10 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,16 @@ ANTHROPIC_API_VERSION=
### anthropic claude Api url (optional)
ANTHROPIC_URL=

# (optional)
# Default: Empty
# MiniMax API key, set if you want to use MiniMax models.
MINIMAX_API_KEY=

# (optional)
# Default: https://api.minimax.io
# MiniMax API url, set if you want to customize MiniMax API url.
MINIMAX_URL=

### (optional)
WHITE_WEBDEV_ENDPOINTS=

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

### 已支持
- [x] 原`ChatGPT-Next-Web`所有功能
- [x] MiniMax(MiniMax-M2.7 / MiniMax-M2.5 / MiniMax-M2.5-highspeed)
- [x] StabilityAI
- [x] 支持 Stable Image Ultra
- [x] 支持 Stable Image Core
Expand All @@ -47,6 +48,8 @@
MJ Proxy的API链接地址
### `MJ_PROXY_KEY`
MJ Proxy的API密钥
### `MINIMAX_API_KEY`
(可选)MiniMax API密钥,用于使用MiniMax模型(如MiniMax-M2.7、MiniMax-M2.5)
### `CODE`
(可选)设置页面中的访问密码
### `...其余参数`
Expand Down
3 changes: 3 additions & 0 deletions README_EN.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ One-click to own your own `ChatGPT` + `many AI` aggregation web service (based o

### Already supported
- [x] All functions of the original `ChatGPT-Next-Web`
- [x] MiniMax (MiniMax-M2.7 / MiniMax-M2.5 / MiniMax-M2.5-highspeed)
- [x] StabilityAI
- [x] Support for Stable Image Ultra
- [x] Support for Stable Image Core
Expand All @@ -47,6 +48,8 @@ One-click to own your own `ChatGPT` + `many AI` aggregation web service (based o
MJ Proxy API link address
### `MJ_PROXY_KEY`
MJ Proxy API key
### `MINIMAX_API_KEY`
(Optional) MiniMax API key for using MiniMax models (MiniMax-M2.7, MiniMax-M2.5, etc.)
### `CODE`
(Optional) Set the access password on the page
### `...Other parameters`
Expand Down
3 changes: 3 additions & 0 deletions app/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
case ModelProvider.Qwen:
systemApiKey = serverConfig.alibabaApiKey;
break;
case ModelProvider.MiniMax:
systemApiKey = serverConfig.minimaxApiKey;
break;
case ModelProvider.GPT:
default:
if (req.nextUrl.pathname.includes("azure/deployments")) {
Expand Down
153 changes: 153 additions & 0 deletions app/api/minimax/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import { getServerSideConfig } from "@/app/config/server";
import {
MINIMAX_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 { isModelAvailableInServer } from "@/app/utils/model";

const serverConfig = getServerSideConfig();

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

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

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

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

export const GET = handle;
export const POST = handle;

export const runtime = "edge";
export const preferredRegion = [
"arn1",
"bom1",
"cdg1",
"cle1",
"cpt1",
"dub1",
"fra1",
"gru1",
"hnd1",
"iad1",
"icn1",
"kix1",
"lhr1",
"pdx1",
"sfo1",
"sin1",
"syd1",
];

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

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

let baseUrl = serverConfig.minimaxUrl || MINIMAX_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 (
isModelAvailableInServer(
serverConfig.customModels,
jsonBody?.model as string,
ServiceProvider.MiniMax as string,
)
) {
return NextResponse.json(
{
error: true,
message: `you are not allowed to use ${jsonBody?.model} model`,
},
{
status: 403,
},
);
}
} catch (e) {
console.error(`[MiniMax] filter`, e);
}
}

try {
const res = await fetch(fetchUrl, fetchOptions);

// 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,
});
} finally {
clearTimeout(timeoutId);
}
}
9 changes: 9 additions & 0 deletions app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ClaudeApi } from "./platforms/anthropic";
import { ErnieApi } from "./platforms/baidu";
import { DoubaoApi } from "./platforms/bytedance";
import { QwenApi } from "./platforms/alibaba";
import { MiniMaxApi } from "./platforms/minimax";

export const ROLES = ["system", "user", "assistant"] as const;
export type MessageRole = (typeof ROLES)[number];
Expand Down Expand Up @@ -117,6 +118,9 @@ export class ClientApi {
case ModelProvider.Qwen:
this.llm = new QwenApi();
break;
case ModelProvider.MiniMax:
this.llm = new MiniMaxApi();
break;
default:
this.llm = new ChatGPTApi();
}
Expand Down Expand Up @@ -199,6 +203,7 @@ export function getHeaders() {
const isBaidu = modelConfig.providerName == ServiceProvider.Baidu;
const isByteDance = modelConfig.providerName === ServiceProvider.ByteDance;
const isAlibaba = modelConfig.providerName === ServiceProvider.Alibaba;
const isMiniMax = modelConfig.providerName === ServiceProvider.MiniMax;
const isEnabledAccessControl = accessStore.enabledAccessControl();
const apiKey = isGoogle
? accessStore.googleApiKey
Expand All @@ -210,6 +215,8 @@ export function getHeaders() {
? accessStore.bytedanceApiKey
: isAlibaba
? accessStore.alibabaApiKey
: isMiniMax
? accessStore.minimaxApiKey
: accessStore.openaiApiKey;
return {
isGoogle,
Expand Down Expand Up @@ -267,6 +274,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi {
return new ClientApi(ModelProvider.Doubao);
case ServiceProvider.Alibaba:
return new ClientApi(ModelProvider.Qwen);
case ServiceProvider.MiniMax:
return new ClientApi(ModelProvider.MiniMax);
default:
return new ClientApi(ModelProvider.GPT);
}
Expand Down
Loading