diff --git a/.env.template b/.env.template index 96dedddc8aa..5368c5d3718 100644 --- a/.env.template +++ b/.env.template @@ -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= diff --git a/README.md b/README.md index 01a223d3b63..b1e04ef5e86 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/README_CN.md b/README_CN.md index f4c441ad006..d8ee2b09a11 100644 --- a/README_CN.md +++ b/README_CN.md @@ -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 即可。 diff --git a/README_JA.md b/README_JA.md index 917cc1262c0..4ac984abfca 100644 --- a/README_JA.md +++ b/README_JA.md @@ -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 に設定します。 diff --git a/README_KO.md b/README_KO.md index 40ba5a6a1c4..9d99e7ead2a 100644 --- a/README_KO.md +++ b/README_KO.md @@ -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` (선택 사항) > 기본값: 비어 있음 diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts index e8af34f29f8..fb735e76164 100644 --- a/app/api/[provider]/[...path]/route.ts +++ b/app/api/[provider]/[...path]/route.ts @@ -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, @@ -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 }); } diff --git a/app/api/auth.ts b/app/api/auth.ts index 8c78c70c865..28737c9a104 100644 --- a/app/api/auth.ts +++ b/app/api/auth.ts @@ -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; diff --git a/app/api/baichuan.ts b/app/api/baichuan.ts new file mode 100644 index 00000000000..260ba15dc63 --- /dev/null +++ b/app/api/baichuan.ts @@ -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); + } +} + diff --git a/app/client/api.ts b/app/client/api.ts index f60b0e2ad71..c8515cfddc6 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -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]; @@ -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(); } @@ -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 @@ -297,6 +302,8 @@ export function getHeaders(ignoreHeaders: boolean = false) { : "" : isAI302 ? accessStore.ai302ApiKey + : isBaichuan + ? accessStore.baichuanApiKey : accessStore.openaiApiKey; return { isGoogle, @@ -312,6 +319,7 @@ export function getHeaders(ignoreHeaders: boolean = false) { isChatGLM, isSiliconFlow, isAI302, + isBaichuan, apiKey, isEnabledAccessControl, }; @@ -341,6 +349,7 @@ export function getHeaders(ignoreHeaders: boolean = false) { isChatGLM, isSiliconFlow, isAI302, + isBaichuan, apiKey, isEnabledAccessControl, } = getConfig(); @@ -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); } diff --git a/app/client/platforms/baichuan.ts b/app/client/platforms/baichuan.ts new file mode 100644 index 00000000000..61ddab717d2 --- /dev/null +++ b/app/client/platforms/baichuan.ts @@ -0,0 +1,272 @@ +"use client"; +import { ApiPath, BAICHUAN_BASE_URL, Baichuan } from "@/app/constant"; +import { + useAccessStore, + useAppConfig, + useChatStore, + ChatMessageTool, + usePluginStore, +} from "@/app/store"; +import { streamWithThink } from "@/app/utils/chat"; +import { + ChatOptions, + getHeaders, + LLMApi, + LLMModel, + SpeechOptions, +} from "../api"; +import { getClientConfig } from "@/app/config/client"; +import { + getMessageTextContent, + getMessageTextContentWithoutThinking, + getTimeoutMSByModel, +} from "@/app/utils"; +import { RequestPayload } from "./openai"; +import { fetch } from "@/app/utils/stream"; + +export class BaichuanApi implements LLMApi { + private disableListModels = true; + + path(path: string): string { + const accessStore = useAccessStore.getState(); + + let baseUrl = ""; + + if (accessStore.useCustomConfig) { + baseUrl = accessStore.baichuanUrl; + } + + if (baseUrl.length === 0) { + const isApp = !!getClientConfig()?.isApp; + const apiPath = ApiPath.Baichuan; + baseUrl = isApp ? BAICHUAN_BASE_URL : apiPath; + } + + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.slice(0, baseUrl.length - 1); + } + if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Baichuan)) { + baseUrl = "https://" + baseUrl; + } + + console.log("[Proxy Endpoint] ", baseUrl, path); + + return [baseUrl, path].join("/"); + } + + extractMessage(res: any) { + return res.choices?.at(0)?.message?.content ?? ""; + } + + speech(options: SpeechOptions): Promise { + throw new Error("Method not implemented."); + } + + async chat(options: ChatOptions) { + const messages: ChatOptions["messages"] = []; + for (const v of options.messages) { + if (v.role === "assistant") { + const content = getMessageTextContentWithoutThinking(v); + messages.push({ role: v.role, content }); + } else { + const content = getMessageTextContent(v); + messages.push({ role: v.role, content }); + } + } + + // 检测并修复消息顺序,确保除system外的第一个消息是user + const filteredMessages: ChatOptions["messages"] = []; + let hasFoundFirstUser = false; + + for (const msg of messages) { + if (msg.role === "system") { + // Keep all system messages + filteredMessages.push(msg); + } else if (msg.role === "user") { + // User message directly added + filteredMessages.push(msg); + hasFoundFirstUser = true; + } else if (hasFoundFirstUser) { + // After finding the first user message, all subsequent non-system messages are retained. + filteredMessages.push(msg); + } + // If hasFoundFirstUser is false and it is not a system message, it will be skipped. + } + + const modelConfig = { + ...useAppConfig.getState().modelConfig, + ...useChatStore.getState().currentSession().mask.modelConfig, + ...{ + model: options.config.model, + providerName: options.config.providerName, + }, + }; + + // Baichuan API 参数配置(移除不支持的参数) + const requestPayload: RequestPayload = { + messages: filteredMessages, + stream: options.config.stream, + model: modelConfig.model, + temperature: modelConfig.temperature, + top_p: modelConfig.top_p, + // 注意:Baichuan 不支持 presence_penalty 和 frequency_penalty + }; + + console.log("[Request] baichuan payload: ", requestPayload); + + const shouldStream = !!options.config.stream; + const controller = new AbortController(); + options.onController?.(controller); + + try { + const chatPath = this.path(Baichuan.ChatPath); + const chatPayload = { + method: "POST", + body: JSON.stringify(requestPayload), + signal: controller.signal, + headers: getHeaders(), + }; + + // make a fetch request + const requestTimeoutId = setTimeout( + () => controller.abort(), + getTimeoutMSByModel(options.config.model), + ); + + if (shouldStream) { + const [tools, funcs] = usePluginStore + .getState() + .getAsTools( + useChatStore.getState().currentSession().mask?.plugin || [], + ); + return streamWithThink( + chatPath, + requestPayload, + getHeaders(), + tools as any, + funcs, + controller, + // parseSSE + (text: string, runTools: ChatMessageTool[]) => { + const json = JSON.parse(text); + const choices = json.choices as Array<{ + delta: { + content: string | null; + tool_calls: ChatMessageTool[]; + reasoning_content: string | null; + }; + }>; + const tool_calls = choices[0]?.delta?.tool_calls; + if (tool_calls?.length > 0) { + const index = tool_calls[0]?.index; + const id = tool_calls[0]?.id; + const args = tool_calls[0]?.function?.arguments; + if (id) { + runTools.push({ + id, + type: tool_calls[0]?.type, + function: { + name: tool_calls[0]?.function?.name as string, + arguments: args, + }, + }); + } else { + // @ts-ignore + runTools[index]["function"]["arguments"] += args; + } + } + + // 支持 Baichuan-M2 思考模式 + const reasoning = choices[0]?.delta?.reasoning_content; + const content = choices[0]?.delta?.content; + + // Skip if both content and reasoning_content are empty or null + if ( + (!reasoning || reasoning.length === 0) && + (!content || content.length === 0) + ) { + return { + isThinking: false, + content: "", + }; + } + + if (reasoning && reasoning.length > 0) { + return { + isThinking: true, + content: reasoning, + }; + } else if (content && content.length > 0) { + return { + isThinking: false, + content: content, + }; + } + + return { + isThinking: false, + content: "", + }; + }, + // processToolMessage, include tool_calls message and tool call results + ( + requestPayload: RequestPayload, + toolCallMessage: any, + toolCallResult: any[], + ) => { + // @ts-ignore + requestPayload?.messages?.splice( + // @ts-ignore + requestPayload?.messages?.length, + 0, + toolCallMessage, + ...toolCallResult, + ); + }, + options, + ); + } else { + const res = await fetch(chatPath, chatPayload); + clearTimeout(requestTimeoutId); + + // 检查响应状态 + if (!res.ok) { + const errorText = await res.text(); + let errorMessage = `Baichuan API Error: ${res.status}`; + try { + const errorJson = JSON.parse(errorText); + errorMessage = errorJson.error?.message || errorJson.message || errorMessage; + } catch (e) { + errorMessage = errorText || errorMessage; + } + throw new Error(errorMessage); + } + + const resJson = await res.json(); + + // 检查返回的数据是否有效 + if (!resJson || resJson.error) { + throw new Error(resJson?.message || resJson?.error?.message || "Empty response from server"); + } + + const message = this.extractMessage(resJson); + options.onFinish(message, res); + } + } catch (e: any) { + console.log("[Request] failed to make a chat request", e); + const errorMessage = e?.message || "Unknown error occurred"; + options.onError?.(new Error(errorMessage)); + } + } + async usage() { + return { + used: 0, + total: 0, + }; + } + + async models(): Promise { + return []; + } +} + diff --git a/app/components/emoji.tsx b/app/components/emoji.tsx index 31d7f0ac61e..d86d83422d0 100644 --- a/app/components/emoji.tsx +++ b/app/components/emoji.tsx @@ -21,6 +21,7 @@ import BotIconGrok from "../icons/llm-icons/grok.svg"; import BotIconHunyuan from "../icons/llm-icons/hunyuan.svg"; import BotIconDoubao from "../icons/llm-icons/doubao.svg"; import BotIconChatglm from "../icons/llm-icons/chatglm.svg"; +import BotIconBaichuan from "../icons/llm-icons/baichuan.svg"; export function getEmojiUrl(unified: string, style: EmojiStyle) { // Whoever owns this Content Delivery Network (CDN), I am using your CDN to serve emojis @@ -90,6 +91,8 @@ export function Avatar(props: { model?: ModelType; avatar?: string }) { modelName.startsWith("cogvideox-") ) { LlmIcon = BotIconChatglm; + } else if (modelName.startsWith("baichuan")) { + LlmIcon = BotIconBaichuan; } return ( diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 881c12caeb3..fb37fddac59 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -76,6 +76,7 @@ import { DeepSeek, SiliconFlow, AI302, + Baichuan, } from "../constant"; import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { ErrorBoundary } from "./error"; @@ -1499,6 +1500,46 @@ export function Settings() { ); + const baichuanConfigComponent = accessStore.provider === ServiceProvider.Baichuan && ( + <> + + + accessStore.update( + (access) => (access.baichuanUrl = e.currentTarget.value), + ) + } + > + + + { + accessStore.update( + (access) => (access.baichuanApiKey = e.currentTarget.value), + ); + }} + /> + + + ); + return (
@@ -1864,6 +1905,7 @@ export function Settings() { {chatglmConfigComponent} {siliconflowConfigComponent} {ai302ConfigComponent} + {baichuanConfigComponent} )} diff --git a/app/config/server.ts b/app/config/server.ts index 14175eadc8c..1965d96ab8b 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -92,6 +92,10 @@ declare global { AI302_URL?: string; AI302_API_KEY?: string; + // Baichuan only + BAICHUAN_URL?: string; + BAICHUAN_API_KEY?: string; + // custom template for preprocessing user input DEFAULT_INPUT_TEMPLATE?: string; @@ -168,6 +172,7 @@ export const getServerSideConfig = () => { const isChatGLM = !!process.env.CHATGLM_API_KEY; const isSiliconFlow = !!process.env.SILICONFLOW_API_KEY; const isAI302 = !!process.env.AI302_API_KEY; + const isBaichuan = !!process.env.BAICHUAN_API_KEY; // const apiKeyEnvVar = process.env.OPENAI_API_KEY ?? ""; // const apiKeys = apiKeyEnvVar.split(",").map((v) => v.trim()); // const randomIndex = Math.floor(Math.random() * apiKeys.length); @@ -255,6 +260,10 @@ export const getServerSideConfig = () => { ai302Url: process.env.AI302_URL, ai302ApiKey: getApiKey(process.env.AI302_API_KEY), + isBaichuan, + baichuanUrl: process.env.BAICHUAN_URL, + baichuanApiKey: getApiKey(process.env.BAICHUAN_API_KEY), + gtmId: process.env.GTM_ID, gaId: process.env.GA_ID || DEFAULT_GA_ID, diff --git a/app/constant.ts b/app/constant.ts index db9842d6027..612afc0aa38 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -38,6 +38,8 @@ export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn"; export const AI302_BASE_URL = "https://api.302.ai"; +export const BAICHUAN_BASE_URL = "https://api.baichuan-ai.com"; + export const CACHE_URL_PREFIX = "/api/cache"; export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`; @@ -75,6 +77,7 @@ export enum ApiPath { DeepSeek = "/api/deepseek", SiliconFlow = "/api/siliconflow", "302.AI" = "/api/302ai", + Baichuan = "/api/baichuan", } export enum SlotID { @@ -134,6 +137,7 @@ export enum ServiceProvider { DeepSeek = "DeepSeek", SiliconFlow = "SiliconFlow", "302.AI" = "302.AI", + Baichuan = "Baichuan", } // Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings @@ -161,6 +165,7 @@ export enum ModelProvider { DeepSeek = "DeepSeek", SiliconFlow = "SiliconFlow", "302.AI" = "302.AI", + Baichuan = "Baichuan", } export const Stability = { @@ -278,6 +283,11 @@ export const AI302 = { ListModelPath: "v1/models?llm=1", }; +export const Baichuan = { + ExampleEndpoint: BAICHUAN_BASE_URL, + ChatPath: "v1/chat/completions", +}; + export const DEFAULT_INPUT_TEMPLATE = `{{input}}`; // input / time / model / lang // export const DEFAULT_SYSTEM_TEMPLATE = ` // You are ChatGPT, a large language model trained by {{ServiceProvider}}. @@ -423,6 +433,7 @@ You are an AI assistant with access to system tools. Your role is to help users export const SUMMARIZE_MODEL = "gpt-4o-mini"; export const GEMINI_SUMMARIZE_MODEL = "gemini-pro"; export const DEEPSEEK_SUMMARIZE_MODEL = "deepseek-chat"; +export const BAICHUAN_SUMMARIZE_MODEL = "Baichuan4-Turbo"; export const KnowledgeCutOffDate: Record = { default: "2021-09", @@ -459,6 +470,14 @@ export const KnowledgeCutOffDate: Record = { "gemini-pro-vision": "2023-12", "deepseek-chat": "2024-07", "deepseek-coder": "2024-07", + "Baichuan-M2-Plus": "2025-10", + "Baichuan-M2": "2025-08", + "Baichuan4-Turbo": "2024-10", + "Baichuan4-Air": "2024-10", + Baichuan4: "2024-05", + "Baichuan3-Turbo": "2024-05", + "Baichuan3-Turbo-128k": "2024-05", + "Baichuan2-Turbo": "2024-02", }; export const DEFAULT_TTS_ENGINE = "OpenAI-TTS"; @@ -493,7 +512,7 @@ export const VISION_MODEL_REGEXES = [ /o3/, /o4-mini/, /grok-4/i, - /gpt-5/ + /gpt-5/, ]; export const EXCLUDE_VISION_MODEL_REGEXES = [/claude-3-5-haiku-20241022/]; @@ -561,7 +580,7 @@ const googleModels = [ "gemini-2.0-pro-exp", "gemini-2.0-pro-exp-02-05", "gemini-2.5-pro-preview-06-05", - "gemini-2.5-pro" + "gemini-2.5-pro", ]; const anthropicModels = [ @@ -652,6 +671,17 @@ const iflytekModels = [ const deepseekModels = ["deepseek-chat", "deepseek-coder", "deepseek-reasoner"]; +const baichuanModels = [ + "Baichuan-M2-Plus", + "Baichuan-M2", + "Baichuan4-Turbo", + "Baichuan4-Air", + "Baichuan4", + "Baichuan3-Turbo", + "Baichuan3-Turbo-128k", + "Baichuan2-Turbo", +]; + const xAIModes = [ "grok-beta", "grok-2", @@ -909,6 +939,17 @@ export const DEFAULT_MODELS = [ sorted: 15, }, })), + ...baichuanModels.map((name) => ({ + name, + available: true, + sorted: seq++, + provider: { + id: "baichuan", + providerName: "Baichuan", + providerType: "baichuan", + sorted: 16, + }, + })), ] as const; export const CHAT_PAGE_SIZE = 15; diff --git a/app/icons/llm-icons/baichuan.svg b/app/icons/llm-icons/baichuan.svg new file mode 100644 index 00000000000..5cc4fd32390 --- /dev/null +++ b/app/icons/llm-icons/baichuan.svg @@ -0,0 +1,13 @@ + + Baichuan + + + + + + + + + + + diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 2cb7dd1e535..2cafefce2bb 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -549,6 +549,17 @@ const cn = { SubTitle: "样例:", }, }, + Baichuan: { + ApiKey: { + Title: "接口密钥", + SubTitle: "使用自定义Baichuan API Key", + Placeholder: "Baichuan API Key", + }, + Endpoint: { + Title: "接口地址", + SubTitle: "样例:", + }, + }, }, Model: "模型 (model)", diff --git a/app/locales/en.ts b/app/locales/en.ts index a6d1919045c..7a9850e8422 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -554,6 +554,17 @@ const en: LocaleType = { SubTitle: "Example: ", }, }, + Baichuan: { + ApiKey: { + Title: "Baichuan API Key", + SubTitle: "Use a custom Baichuan API Key", + Placeholder: "Baichuan API Key", + }, + Endpoint: { + Title: "Endpoint Address", + SubTitle: "Example: ", + }, + }, }, Model: "Model", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index d09465d9e1f..460f3722beb 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -393,6 +393,17 @@ const tw = { SubTitle: "範例:", }, }, + Baichuan: { + ApiKey: { + Title: "API 金鑰", + SubTitle: "使用自訂 Baichuan API 金鑰", + Placeholder: "Baichuan API 金鑰", + }, + Endpoint: { + Title: "端點位址", + SubTitle: "範例:", + }, + }, CustomModel: { Title: "自訂模型名稱", SubTitle: "增加自訂模型可選擇項目,使用英文逗號隔開", diff --git a/app/store/access.ts b/app/store/access.ts index fd55fbdd3d1..6c309b16544 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -18,6 +18,7 @@ import { CHATGLM_BASE_URL, SILICONFLOW_BASE_URL, AI302_BASE_URL, + BAICHUAN_BASE_URL, } from "../constant"; import { getHeaders } from "../client/api"; import { getClientConfig } from "../config/client"; @@ -62,6 +63,8 @@ const DEFAULT_SILICONFLOW_URL = isApp const DEFAULT_AI302_URL = isApp ? AI302_BASE_URL : ApiPath["302.AI"]; +const DEFAULT_BAICHUAN_URL = isApp ? BAICHUAN_BASE_URL : ApiPath.Baichuan; + const DEFAULT_ACCESS_STATE = { accessCode: "", useCustomConfig: false, @@ -139,6 +142,10 @@ const DEFAULT_ACCESS_STATE = { ai302Url: DEFAULT_AI302_URL, ai302ApiKey: "", + // baichuan + baichuanUrl: DEFAULT_BAICHUAN_URL, + baichuanApiKey: "", + // server config needCode: true, hideUserApiKey: false, @@ -226,6 +233,10 @@ export const useAccessStore = createPersistStore( return ensure(get(), ["siliconflowApiKey"]); }, + isValidBaichuan() { + return ensure(get(), ["baichuanApiKey"]); + }, + isAuthorized() { this.fetch(); @@ -245,6 +256,7 @@ export const useAccessStore = createPersistStore( this.isValidXAI() || this.isValidChatGLM() || this.isValidSiliconFlow() || + this.isValidBaichuan() || !this.enabledAccessControl() || (this.enabledAccessControl() && ensure(get(), ["accessCode"])) );