diff --git a/.env.template b/.env.template index 4efaa2ff8c6..5223b0401e1 100644 --- a/.env.template +++ b/.env.template @@ -81,3 +81,7 @@ SILICONFLOW_API_KEY= ### siliconflow Api url (optional) SILICONFLOW_URL= + +HUAWEI_URL= + +HUAWEI_API_KEY= diff --git a/app/api/[provider]/[...path]/route.ts b/app/api/[provider]/[...path]/route.ts index 8975bf971e2..70bb96652b3 100644 --- a/app/api/[provider]/[...path]/route.ts +++ b/app/api/[provider]/[...path]/route.ts @@ -15,6 +15,7 @@ import { handle as siliconflowHandler } from "../../siliconflow"; import { handle as xaiHandler } from "../../xai"; import { handle as chatglmHandler } from "../../glm"; import { handle as proxyHandler } from "../../proxy"; +import { handle as huaweiHandler } from "../../huawei"; async function handle( req: NextRequest, @@ -52,6 +53,8 @@ async function handle( return siliconflowHandler(req, { params }); case ApiPath.OpenAI: return openaiHandler(req, { params }); + case ApiPath.Huawei: + return huaweiHandler(req, { params }); default: return proxyHandler(req, { params }); } diff --git a/app/api/huawei.ts b/app/api/huawei.ts new file mode 100644 index 00000000000..697f8b2633c --- /dev/null +++ b/app/api/huawei.ts @@ -0,0 +1,176 @@ +import { getServerSideConfig } from "@/app/config/server"; +import { + HUAWEI_BASE_URL, + ApiPath, + ModelProvider, + Huawei, +} from "@/app/constant"; +import { prettyObject } from "@/app/utils/format"; +import { NextRequest, NextResponse } from "next/server"; +import { auth } from "@/app/api/auth"; + +const serverConfig = getServerSideConfig(); + +export async function handle( + req: NextRequest, + { params }: { params: { path: string[] } }, +) { + console.log("[Huawei Route] params ", params); + + if (req.method === "OPTIONS") { + return NextResponse.json({ body: "OK" }, { status: 200 }); + } + + const authResult = auth(req, ModelProvider.Huawei); + if (authResult.error) { + return NextResponse.json(authResult, { + status: 401, + }); + } + + try { + const response = await request(req); + return response; + } catch (e) { + console.error("[Huawei] ", e); + return NextResponse.json(prettyObject(e)); + } +} +async function request(req: NextRequest) { + const controller = new AbortController(); + + let path = `${req.nextUrl.pathname}`.replaceAll(ApiPath.Huawei, ""); + const bodyText = await req.text(); + const body = JSON.parse(bodyText); + let modelName = body.model as string; + + // 先用原始 modelName 获取 charUrl + let baseUrl: string; + let endpoint = ""; + if (modelName === "DeepSeek-V3") { + endpoint = "271c9332-4aa6-4ff5-95b3-0cf8bd94c394"; + } + if (modelName === "DeepSeek-R1") { + endpoint = "8a062fd4-7367-4ab4-a936-5eeb8fb821c4"; + } + + + let charUrl = HUAWEI_BASE_URL.concat("/") + .concat(endpoint) + .concat("/v1/chat/completions") + .replace(/(? ({ + role: msg.role, + content: msg.content, + })) + .filter((msg: any) => msg.role !== "system"), + model: modelName.replace(/^(DeepSeek-(?:R1|V3)).*$/, "$1"), // 只保留 DeepSeek-R1 或 DeepSeek-V3 + stream: body.stream, + temperature: body.temperature, + presence_penalty: body.presence_penalty, + frequency_penalty: body.frequency_penalty, + top_p: body.top_p, + }; + const modifiedBodyText = JSON.stringify(modifiedBody); + console.log("Modified request body:", modifiedBodyText); + + // if(!baseUrl){ + // baseUrl = HUAWEI_BASE_URL + // } + // baseUrl = Huawei.ChatPath(modelName) || serverConfig.huaweiUrl || HUAWEI_BASE_URL; + console.log( + `current model name:${modelName},current api path:${baseUrl}.........`, + ); + 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, + ); + + // 如果 baseUrl 来自 Huawei.ChatPath,则不需要再拼接 path + let fetchUrl = baseUrl.includes(HUAWEI_BASE_URL) + ? baseUrl + : `${baseUrl}${path}`; + + const headers: Record = { + "Content-Type": "application/json", + Authorization: req.headers.get("Authorization") ?? "", + "X-Forwarded-For": req.headers.get("X-Forwarded-For") ?? "", + "X-Real-IP": req.headers.get("X-Real-IP") ?? "", + "User-Agent": req.headers.get("User-Agent") ?? "", + }; + console.debug(`headers.Authorization:${headers.Authorization}`); + console.debug("serverConfig.huaweiApiKey: *****"); + // 如果没有 Authorization header,使用系统配置的 API key + + headers.Authorization = `Bearer ${serverConfig.huaweiApiKey}`; + + // #1815 try to refuse some request to some models + // if (serverConfig.customModels) { + // try { + // const jsonBody = JSON.parse(bodyText); // 直接使用已解析的 body + // + // if ( + // isModelNotavailableInServer( + // serverConfig.customModels, + // jsonBody?.model as string, + // ServiceProvider.Huawei as string, + // ) + // ) { + // return NextResponse.json( + // { + // error: true, + // message: `you are not allowed to use ${jsonBody?.model} model`, + // }, + // { + // status: 403, + // }, + // ); + // } + // } catch (e) { + // console.error(`[Huawei] filter`, e); + // } + // } + try { + const res = await fetch(fetchUrl, { + headers, + method: req.method, + body: modifiedBodyText, + redirect: "manual", + // @ts-ignore + duplex: "half", + signal: controller.signal, + }); + 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); + } +} +export { Huawei }; diff --git a/app/client/api.ts b/app/client/api.ts index f5288593d32..e3777b42b8f 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -24,6 +24,7 @@ import { DeepSeekApi } from "./platforms/deepseek"; import { XAIApi } from "./platforms/xai"; import { ChatGLMApi } from "./platforms/glm"; import { SiliconflowApi } from "./platforms/siliconflow"; +import { HuaweiApi } from "./platforms/huawei"; export const ROLES = ["system", "user", "assistant"] as const; export type MessageRole = (typeof ROLES)[number]; @@ -173,6 +174,9 @@ export class ClientApi { case ModelProvider.SiliconFlow: this.llm = new SiliconflowApi(); break; + case ModelProvider.Huawei: + this.llm = new HuaweiApi(); + break; default: this.llm = new ChatGPTApi(); } @@ -265,6 +269,7 @@ export function getHeaders(ignoreHeaders: boolean = false) { const isChatGLM = modelConfig.providerName === ServiceProvider.ChatGLM; const isSiliconFlow = modelConfig.providerName === ServiceProvider.SiliconFlow; + const isHuawei = modelConfig.providerName == ServiceProvider.Huawei; const isEnabledAccessControl = accessStore.enabledAccessControl(); const apiKey = isGoogle ? accessStore.googleApiKey @@ -290,6 +295,8 @@ export function getHeaders(ignoreHeaders: boolean = false) { ? accessStore.iflytekApiKey && accessStore.iflytekApiSecret ? accessStore.iflytekApiKey + ":" + accessStore.iflytekApiSecret : "" + : isHuawei + ? accessStore.huaweiApiKey : accessStore.openaiApiKey; return { isGoogle, @@ -304,6 +311,7 @@ export function getHeaders(ignoreHeaders: boolean = false) { isXAI, isChatGLM, isSiliconFlow, + isHuawei, apiKey, isEnabledAccessControl, }; @@ -332,6 +340,7 @@ export function getHeaders(ignoreHeaders: boolean = false) { isXAI, isChatGLM, isSiliconFlow, + isHuawei: boolean, apiKey, isEnabledAccessControl, } = getConfig(); @@ -382,6 +391,8 @@ export function getClientApi(provider: ServiceProvider): ClientApi { return new ClientApi(ModelProvider.ChatGLM); case ServiceProvider.SiliconFlow: return new ClientApi(ModelProvider.SiliconFlow); + case ServiceProvider.Huawei: + return new ClientApi(ModelProvider.Huawei); default: return new ClientApi(ModelProvider.GPT); } diff --git a/app/client/platforms/huawei.ts b/app/client/platforms/huawei.ts new file mode 100644 index 00000000000..1896369ecf3 --- /dev/null +++ b/app/client/platforms/huawei.ts @@ -0,0 +1,200 @@ +"use client"; + +import { ApiPath, HUAWEI_BASE_URL, Huawei } from "@/app/constant"; +import { + useAccessStore, + useAppConfig, + useChatStore, + usePluginStore, + ChatMessageTool, +} from "@/app/store"; +import { + ChatOptions, + getHeaders, + LLMApi, + LLMModel, + MultimodalContent, + SpeechOptions, +} from "../api"; +import { getClientConfig } from "@/app/config/client"; +import { getTimeoutMSByModel } from "@/app/utils"; +import { streamWithThink } from "@/app/utils/chat"; +import { fetch } from "@/app/utils/stream"; + +interface RequestPayloadForHuawei { + messages: { + role: "system" | "user" | "assistant"; + content: string | MultimodalContent[]; + }[]; + stream?: boolean; + model: string; + temperature: number; + presence_penalty: number; + frequency_penalty: number; + top_p: number; + max_tokens?: number; +} + +export class HuaweiApi implements LLMApi { + path(path: string): string { + const accessStore = useAccessStore.getState(); + + let baseUrl = ""; + + if (accessStore.useCustomConfig) { + baseUrl = accessStore.huaweiUrl; + } + + if (baseUrl.length === 0) { + const isApp = !!getClientConfig()?.isApp; + baseUrl = isApp ? HUAWEI_BASE_URL : ApiPath.Huawei; + } + + if (baseUrl.endsWith("/")) { + baseUrl = baseUrl.slice(0, baseUrl.length - 1); + } + if (!baseUrl.startsWith("http") && !baseUrl.startsWith(ApiPath.Huawei)) { + 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 = options.messages.map((v) => ({ + role: v.role, + content: v.content, + })); + + const modelConfig = { + ...useAppConfig.getState().modelConfig, + ...useChatStore.getState().currentSession().mask.modelConfig, + ...{ + model: options.config.model, + }, + }; + + const requestPayload: RequestPayloadForHuawei = { + messages, + stream: options.config.stream, + model: modelConfig.model, + temperature: modelConfig.temperature, + presence_penalty: modelConfig.presence_penalty, + frequency_penalty: modelConfig.frequency_penalty, + top_p: modelConfig.top_p, + }; + + const shouldStream = !!options.config.stream; + const controller = new AbortController(); + options.onController?.(controller); + + try { + const chatPath = this.path(Huawei.ChatPath); + const chatPayload = { + method: "POST", + body: JSON.stringify(requestPayload), + signal: controller.signal, + headers: getHeaders(), + }; + + 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; + tool_calls: ChatMessageTool[]; + }; + }>; + 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; + } + } + return { + isThinking: false, + content: choices[0]?.delta?.content || "", + }; + }, + // processToolMessage + ( + payload: RequestPayloadForHuawei, + toolCallMessage: any, + toolCallResult: any[], + ) => { + payload?.messages?.splice( + payload?.messages?.length, + 0, + toolCallMessage, + ...toolCallResult, + ); + }, + options, + ); + } else { + const res = await fetch(chatPath, chatPayload); + clearTimeout(requestTimeoutId); + + const resJson = await res.json(); + const message = this.extractMessage(resJson); + options.onFinish(message, res); + } + } catch (e) { + console.log("[Request] failed to make a chat request", e); + options.onError?.(e as Error); + } + } + + async usage() { + return { + used: 0, + total: 0, + }; + } + + async models(): Promise { + return []; + } +} diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 68ebcf084c1..1609adb870a 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -75,6 +75,7 @@ import { ChatGLM, DeepSeek, SiliconFlow, + Huawei, } from "../constant"; import { Prompt, SearchService, usePromptStore } from "../store/prompt"; import { ErrorBoundary } from "./error"; @@ -1457,6 +1458,46 @@ export function Settings() { ); + const huaweiConfigComponent = accessStore.provider === + ServiceProvider.Huawei && ( + <> + + + accessStore.update( + (access) => (access.huaweiUrl = e.currentTarget.value), + ) + } + > + + + { + accessStore.update( + (access) => (access.huaweiApiKey = e.currentTarget.value), + ); + }} + /> + + + ); return ( @@ -1822,6 +1863,7 @@ export function Settings() { {XAIConfigComponent} {chatglmConfigComponent} {siliconflowConfigComponent} + {huaweiConfigComponent} )} diff --git a/app/config/server.ts b/app/config/server.ts index 43d4ff833ce..60c251b5476 100644 --- a/app/config/server.ts +++ b/app/config/server.ts @@ -88,6 +88,10 @@ declare global { SILICONFLOW_URL?: string; SILICONFLOW_API_KEY?: string; + //huaweionly + HUAWEI_URL?: string; + HUAWEI_API_KEY?: string; + // custom template for preprocessing user input DEFAULT_INPUT_TEMPLATE?: string; @@ -163,6 +167,7 @@ export const getServerSideConfig = () => { const isXAI = !!process.env.XAI_API_KEY; const isChatGLM = !!process.env.CHATGLM_API_KEY; const isSiliconFlow = !!process.env.SILICONFLOW_API_KEY; + const isHuawei = !!process.env.HUAWEI_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); @@ -233,6 +238,10 @@ export const getServerSideConfig = () => { xaiUrl: process.env.XAI_URL, xaiApiKey: getApiKey(process.env.XAI_API_KEY), + isHuawei, + huaweiUrl: process.env.HUAWEI_URL, + huaweiApiKey: getApiKey(process.env.HUAWEI_API_KEY), + isChatGLM, chatglmUrl: process.env.CHATGLM_URL, chatglmApiKey: getApiKey(process.env.CHATGLM_API_KEY), diff --git a/app/constant.ts b/app/constant.ts index c1b1354850a..0bbafaf690c 100644 --- a/app/constant.ts +++ b/app/constant.ts @@ -36,6 +36,9 @@ export const CHATGLM_BASE_URL = "https://open.bigmodel.cn"; export const SILICONFLOW_BASE_URL = "https://api.siliconflow.cn"; +export const HUAWEI_BASE_URL = + "https://maas-cn-southwest-2.modelarts-maas.com/v1/infers"; + export const CACHE_URL_PREFIX = "/api/cache"; export const UPLOAD_URL = `${CACHE_URL_PREFIX}/upload`; @@ -72,6 +75,7 @@ export enum ApiPath { ChatGLM = "/api/chatglm", DeepSeek = "/api/deepseek", SiliconFlow = "/api/siliconflow", + Huawei = "/api/huawei", } export enum SlotID { @@ -130,6 +134,7 @@ export enum ServiceProvider { ChatGLM = "ChatGLM", DeepSeek = "DeepSeek", SiliconFlow = "SiliconFlow", + Huawei = "Huawei", } // Google API safety settings, see https://ai.google.dev/gemini-api/docs/safety-settings @@ -156,6 +161,7 @@ export enum ModelProvider { ChatGLM = "ChatGLM", DeepSeek = "DeepSeek", SiliconFlow = "SiliconFlow", + Huawei = "Huawei", } export const Stability = { @@ -214,6 +220,11 @@ export const Baidu = { }, }; +export const Huawei = { + ExampleEndpoint: HUAWEI_BASE_URL, + ChatPath: "/v1/chat/completions", +}; + export const ByteDance = { ExampleEndpoint: "https://ark.cn-beijing.volces.com/api/", ChatPath: "api/v3/chat/completions", @@ -565,6 +576,11 @@ const bytedanceModels = [ "Doubao-pro-4k", "Doubao-pro-32k", "Doubao-pro-128k", + "deepseek-r1-250120", + "deepseek-v3-241226", + "deepseek-v3-250324", + "deepseek-r1-distill-qwen-7b-250120", + "deepseek-r1-distill-qwen-32b-250120", ]; const alibabaModes = [ @@ -650,6 +666,11 @@ const siliconflowModels = [ "Pro/deepseek-ai/DeepSeek-V3", ]; +const huaweiModels = [ + "DeepSeek-R1", + "DeepSeek-V3", +]; + let seq = 1000; // 内置的模型序号生成器从1000开始 export const DEFAULT_MODELS = [ ...openaiModels.map((name) => ({ @@ -806,6 +827,17 @@ export const DEFAULT_MODELS = [ sorted: 14, }, })), + ...huaweiModels.map((name) => ({ + name, + available: true, + sorted: seq++, + provider: { + id: "huawei", + providerName: "Huawei", + providerType: "Huawei", + sorted: 15, + }, + })), ] as const; export const CHAT_PAGE_SIZE = 15; diff --git a/app/locales/ar.ts b/app/locales/ar.ts index e2fad3494c3..d4f17513dc5 100644 --- a/app/locales/ar.ts +++ b/app/locales/ar.ts @@ -420,6 +420,22 @@ const ar: PartialLocaleType = { Title: "اسم النموذج المخصص", SubTitle: "أضف خيارات نموذج مخصص، مفصولة بفواصل إنجليزية", }, + Huawei: { + ApiKey: { + Title: "مفتاح API", + SubTitle: "استخدم مفتاح API الخاص بـ HUAWEI", + Placeholder: "مفتاح HUAWEI", + }, + SecretKey: { + Title: "المفتاح السري", + SubTitle: "استخدم مفتاح HUAWEI السري الخاص بك", + Placeholder: "المفتاح السري HUAWEI", + }, + Endpoint: { + Title: "عنوان الواجهة", + SubTitle: "لا يدعم التكوين المخصص .env", + }, + }, }, Model: "النموذج", diff --git a/app/locales/bn.ts b/app/locales/bn.ts index f52f101ce35..3c935b4bc66 100644 --- a/app/locales/bn.ts +++ b/app/locales/bn.ts @@ -428,6 +428,22 @@ const bn: PartialLocaleType = { SubTitle: "স্বনির্ধারিত মডেল বিকল্পগুলি যুক্ত করুন, ইংরেজি কমা দ্বারা আলাদা করুন", }, + Huawei: { + ApiKey: { + Title: "এপিআই কী", + SubTitle: "আপনার HUAWEI এপিআই কী ব্যবহার করুন", + Placeholder: "HUAWEI কী", + }, + SecretKey: { + Title: "গোপন কী", + SubTitle: "আপনার HUAWEI গোপন কী ব্যবহার করুন", + Placeholder: "HUAWEI গোপন কী", + }, + Endpoint: { + Title: "এন্ডপয়েন্ট ঠিকানা", + SubTitle: "কাস্টম কনফিগারেশনের জন্য .env-এ যান", + }, + }, }, Model: "মডেল (model)", diff --git a/app/locales/cn.ts b/app/locales/cn.ts index 81b609cde17..399a66bba23 100644 --- a/app/locales/cn.ts +++ b/app/locales/cn.ts @@ -538,6 +538,22 @@ const cn = { Title: "自定义模型名", SubTitle: "增加自定义模型可选项,使用英文逗号隔开", }, + Huawei: { + ApiKey: { + Title: "API Key", + SubTitle: "使用自定义华为API Key", + Placeholder: "HUAWEI Key", + }, + SecretKey: { + Title: "Secret Key", + SubTitle: "使用自定义HUAWEI Secret Key", + Placeholder: "HUAWEI Secret Key", + }, + Endpoint: { + Title: "接口地址", + SubTitle: "不支持自定义前往.env配置", + }, + }, }, Model: "模型 (model)", diff --git a/app/locales/cs.ts b/app/locales/cs.ts index d62a2036710..63ab73314d1 100644 --- a/app/locales/cs.ts +++ b/app/locales/cs.ts @@ -427,6 +427,22 @@ const cs: PartialLocaleType = { Title: "Vlastní názvy modelů", SubTitle: "Přidejte možnosti vlastních modelů, oddělené čárkami", }, + Huawei: { + ApiKey: { + Title: "API Klíč", + SubTitle: "Použijte svůj HUAWEI API klíč", + Placeholder: "HUAWEI Klíč", + }, + SecretKey: { + Title: "Tajný Klíč", + SubTitle: "Použijte svůj HUAWEI Tajný klíč", + Placeholder: "HUAWEI Tajný Klíč", + }, + Endpoint: { + Title: "Adresa rozhraní", + SubTitle: "Nepodporuje vlastní konfiguraci .env", + }, + }, }, Model: "Model (model)", diff --git a/app/locales/da.ts b/app/locales/da.ts index 7090b062bb3..0b9cb4fc91f 100644 --- a/app/locales/da.ts +++ b/app/locales/da.ts @@ -498,6 +498,22 @@ const da: PartialLocaleType = { Title: "Egne modelnavne", SubTitle: "Skriv komma-adskilte navne", }, + Huawei: { + ApiKey: { + Title: "API-nøgle", + SubTitle: "Brug din egen HUAWEI API-nøgle", + Placeholder: "HUAWEI-nøgle", + }, + SecretKey: { + Title: "Hemmelig nøgle", + SubTitle: "Brug din egen HUAWEI hemmelige nøgle", + Placeholder: "HUAWEI hemmelig nøgle", + }, + Endpoint: { + Title: "Grænsefladeadresse", + SubTitle: "Understøtter ikke tilpasset .env-konfiguration", + }, + }, Google: { ApiKey: { Title: "Google-nøgle", diff --git a/app/locales/de.ts b/app/locales/de.ts index 3490190a80a..46ff823ac4c 100644 --- a/app/locales/de.ts +++ b/app/locales/de.ts @@ -439,6 +439,22 @@ const de: PartialLocaleType = { SubTitle: "Fügen Sie benutzerdefinierte Modelloptionen hinzu, getrennt durch Kommas", }, + Huawei: { + ApiKey: { + Title: "API-Schlüssel", + SubTitle: "Verwenden Sie Ihren eigenen HUAWEI API-Schlüssel", + Placeholder: "HUAWEI-Schlüssel", + }, + SecretKey: { + Title: "Geheimer Schlüssel", + SubTitle: "Verwenden Sie Ihren eigenen HUAWEI geheimen Schlüssel", + Placeholder: "HUAWEI geheimer Schlüssel", + }, + Endpoint: { + Title: "Schnittstellenadresse", + SubTitle: "Unterstützt keine benutzerdefinierte .env-Konfiguration", + }, + }, }, Model: "Modell", diff --git a/app/locales/en.ts b/app/locales/en.ts index 8fecf8bf78e..6cbe6650b46 100644 --- a/app/locales/en.ts +++ b/app/locales/en.ts @@ -522,6 +522,22 @@ const en: LocaleType = { Title: "Custom Models", SubTitle: "Custom model options, seperated by comma", }, + Huawei: { + ApiKey: { + Title: "API Key", + SubTitle: "Use your own HUAWEI API key", + Placeholder: "HUAWEI Key", + }, + SecretKey: { + Title: "Secret Key", + SubTitle: "Use your own HUAWEI Secret key", + Placeholder: "HUAWEI Secret Key", + }, + Endpoint: { + Title: "Endpoint Address", + SubTitle: "Does not support custom .env configuration", + }, + }, Google: { ApiKey: { Title: "API Key", diff --git a/app/locales/es.ts b/app/locales/es.ts index 03af9b439ad..3e721a5dbe9 100644 --- a/app/locales/es.ts +++ b/app/locales/es.ts @@ -441,6 +441,22 @@ const es: PartialLocaleType = { SubTitle: "Agrega opciones de modelos personalizados, separados por comas", }, + Huawei: { + ApiKey: { + Title: "Clave API", + SubTitle: "Utiliza tu propia clave API de HUAWEI", + Placeholder: "Clave HUAWEI", + }, + SecretKey: { + Title: "Clave Secreta", + SubTitle: "Utiliza tu propia clave secreta de HUAWEI", + Placeholder: "Clave secreta HUAWEI", + }, + Endpoint: { + Title: "Dirección del Endpoint", + SubTitle: "No admite configuración personalizada .env", + }, + }, }, Model: "Modelo (model)", diff --git a/app/locales/fr.ts b/app/locales/fr.ts index d25c60eb6c8..79d6397afff 100644 --- a/app/locales/fr.ts +++ b/app/locales/fr.ts @@ -440,6 +440,23 @@ const fr: PartialLocaleType = { SubTitle: "Ajouter des options de modèles personnalisés, séparées par des virgules", }, + Huawei: { + ApiKey: { + Title: "Clé API", + SubTitle: "Utilisez votre propre clé API HUAWEI", + Placeholder: "Clé HUAWEI", + }, + SecretKey: { + Title: "Clé secrète", + SubTitle: "Utilisez votre propre clé secrète HUAWEI", + Placeholder: "Clé secrète HUAWEI", + }, + Endpoint: { + Title: "Adresse de l'endpoint", + SubTitle: + "Ne prend pas en charge la configuration personnalisée .env", + }, + }, }, Model: "Modèle", diff --git a/app/locales/id.ts b/app/locales/id.ts index af96fd2725c..8f6651ac5dc 100644 --- a/app/locales/id.ts +++ b/app/locales/id.ts @@ -428,6 +428,22 @@ const id: PartialLocaleType = { Title: "Nama Model Kustom", SubTitle: "Tambahkan opsi model kustom, pisahkan dengan koma", }, + Huawei: { + ApiKey: { + Title: "Kunci API", + SubTitle: "Gunakan kunci API HUAWEI Anda sendiri", + Placeholder: "Kunci HUAWEI", + }, + SecretKey: { + Title: "Kunci Rahasia", + SubTitle: "Gunakan kunci rahasia HUAWEI Anda sendiri", + Placeholder: "Kunci Rahasia HUAWEI", + }, + Endpoint: { + Title: "Alamat Endpoint", + SubTitle: "Tidak mendukung konfigurasi .env kustom", + }, + }, }, Model: "Model", diff --git a/app/locales/it.ts b/app/locales/it.ts index 59bc1eb1594..4c732b1af1c 100644 --- a/app/locales/it.ts +++ b/app/locales/it.ts @@ -441,6 +441,22 @@ const it: PartialLocaleType = { SubTitle: "Aggiungi opzioni di modelli personalizzati, separati da virgole", }, + Huawei: { + ApiKey: { + Title: "Chiave API", + SubTitle: "Usa la tua chiave API HUAWEI", + Placeholder: "Chiave HUAWEI", + }, + SecretKey: { + Title: "Chiave Segreta", + SubTitle: "Usa la tua chiave segreta HUAWEI", + Placeholder: "Chiave segreta HUAWEI", + }, + Endpoint: { + Title: "Indirizzo dell'Endpoint", + SubTitle: "Non supporta configurazioni personalizzate .env", + }, + }, }, Model: "Modello (model)", diff --git a/app/locales/jp.ts b/app/locales/jp.ts index e7c81e186bd..14fafe479ff 100644 --- a/app/locales/jp.ts +++ b/app/locales/jp.ts @@ -424,6 +424,22 @@ const jp: PartialLocaleType = { Title: "カスタムモデル名", SubTitle: "カスタムモデルの選択肢を追加、英語のカンマで区切る", }, + Huawei: { + ApiKey: { + Title: "APIキー", + SubTitle: "自分のHUAWEI APIキーを使用してください", + Placeholder: "HUAWEIキー", + }, + SecretKey: { + Title: "秘密キー", + SubTitle: "自分のHUAWEI秘密キーを使用してください", + Placeholder: "HUAWEI秘密キー", + }, + Endpoint: { + Title: "エンドポイントアドレス", + SubTitle: "カスタム.env構成はサポートされていません", + }, + }, }, Model: "モデル (model)", diff --git a/app/locales/ko.ts b/app/locales/ko.ts index f2c433b766f..c252b974f2d 100644 --- a/app/locales/ko.ts +++ b/app/locales/ko.ts @@ -421,6 +421,22 @@ const ko: PartialLocaleType = { Title: "커스텀 모델 이름", SubTitle: "커스텀 모델 옵션 추가, 영어 쉼표로 구분", }, + Huawei: { + ApiKey: { + Title: "API 키", + SubTitle: "자신의 HUAWEI API 키를 사용하세요", + Placeholder: "HUAWEI 키", + }, + SecretKey: { + Title: "비밀 키", + SubTitle: "자신의 HUAWEI 비밀 키를 사용하세요", + Placeholder: "HUAWEI 비밀 키", + }, + Endpoint: { + Title: "엔드포인트 주소", + SubTitle: "사용자 정의 .env 구성을 지원하지 않습니다", + }, + }, }, Model: "모델 (model)", diff --git a/app/locales/no.ts b/app/locales/no.ts index f056ef12fb3..b9144fc2852 100644 --- a/app/locales/no.ts +++ b/app/locales/no.ts @@ -433,6 +433,22 @@ const no: PartialLocaleType = { Title: "Egendefinert modellnavn", SubTitle: "Legg til egendefinerte modellalternativer, skill med komma", }, + Huawei: { + ApiKey: { + Title: "API-nøkkel", + SubTitle: "Bruk din egen HUAWEI API-nøkkel", + Placeholder: "HUAWEI-nøkkel", + }, + SecretKey: { + Title: "Hemmelig nøkkel", + SubTitle: "Bruk din egen HUAWEI hemmelige nøkkel", + Placeholder: "HUAWEI hemmelig nøkkel", + }, + Endpoint: { + Title: "Grensesnittadresse", + SubTitle: "Støtter ikke tilpasset .env-konfigurasjon", + }, + }, }, Model: "Modell", diff --git a/app/locales/pt.ts b/app/locales/pt.ts index 152f502284c..7e90edb0bbf 100644 --- a/app/locales/pt.ts +++ b/app/locales/pt.ts @@ -363,6 +363,22 @@ const pt: PartialLocaleType = { Title: "Modelos Personalizados", SubTitle: "Opções de modelo personalizado, separados por vírgula", }, + Huawei: { + ApiKey: { + Title: "Chave API", + SubTitle: "Use sua própria chave API HUAWEI", + Placeholder: "Chave HUAWEI", + }, + SecretKey: { + Title: "Chave Secreta", + SubTitle: "Use sua própria chave secreta HUAWEI", + Placeholder: "Chave Secreta HUAWEI", + }, + Endpoint: { + Title: "Endereço do Endpoint", + SubTitle: "Não suporta configuração personalizada .env", + }, + }, }, Model: "Modelo", diff --git a/app/locales/ru.ts b/app/locales/ru.ts index 4294a3b341b..57b7e6e27cc 100644 --- a/app/locales/ru.ts +++ b/app/locales/ru.ts @@ -431,6 +431,22 @@ const ru: PartialLocaleType = { SubTitle: "Добавьте варианты пользовательских моделей, разделяя запятыми", }, + Huawei: { + ApiKey: { + Title: "API Ключ", + SubTitle: "Используйте свой HUAWEI API ключ", + Placeholder: "HUAWEI Ключ", + }, + SecretKey: { + Title: "Секретный Ключ", + SubTitle: "Используйте свой HUAWEI Секретный ключ", + Placeholder: "HUAWEI Секретный Ключ", + }, + Endpoint: { + Title: "Адрес интерфейса", + SubTitle: "Не поддерживает собственную конфигурацию .env", + }, + }, }, Model: "Модель", diff --git a/app/locales/sk.ts b/app/locales/sk.ts index 36454de752a..368a01e069b 100644 --- a/app/locales/sk.ts +++ b/app/locales/sk.ts @@ -363,6 +363,22 @@ const sk: PartialLocaleType = { Title: "Vlastné modely", SubTitle: "Možnosti vlastného modelu, oddelené čiarkou", }, + Huawei: { + ApiKey: { + Title: "API kľúč", + SubTitle: "Použite svoj vlastný HUAWEI API kľúč", + Placeholder: "HUAWEI kľúč", + }, + SecretKey: { + Title: "Tajný kľúč", + SubTitle: "Použite svoj vlastný HUAWEI tajný kľúč", + Placeholder: "HUAWEI tajný kľúč", + }, + Endpoint: { + Title: "Adresa rozhrania", + SubTitle: "Nepodporuje vlastnú konfiguráciu .env", + }, + }, Google: { ApiKey: { Title: "API kľúč", diff --git a/app/locales/tr.ts b/app/locales/tr.ts index 2082488a51c..9cdb79496a2 100644 --- a/app/locales/tr.ts +++ b/app/locales/tr.ts @@ -431,6 +431,22 @@ const tr: PartialLocaleType = { SubTitle: "Özelleştirilmiş model seçenekleri ekleyin, İngilizce virgül ile ayırın", }, + Huawei: { + ApiKey: { + Title: "API Anahtarı", + SubTitle: "Kendi Huawei API Anahtarınızı kullanın", + Placeholder: "HUAWEI Anahtarı", + }, + SecretKey: { + Title: "Gizli Anahtar", + SubTitle: "Kendi HUAWEI Gizli Anahtarınızı kullanın", + Placeholder: "HUAWEI Gizli Anahtarı", + }, + Endpoint: { + Title: "Arayüz Adresi", + SubTitle: "Özelleştirilmiş .env yapılandırmasına desteklenmez", + }, + }, }, Model: "Model (model)", diff --git a/app/locales/tw.ts b/app/locales/tw.ts index 83dd547b8ed..e9f6283ea3b 100644 --- a/app/locales/tw.ts +++ b/app/locales/tw.ts @@ -386,6 +386,22 @@ const tw = { Title: "自訂模型名稱", SubTitle: "增加自訂模型可選擇項目,使用英文逗號隔開", }, + Huawei: { + ApiKey: { + Title: "API金鑰", + SubTitle: "使用您的HUAWEI API金鑰", + Placeholder: "HUAWEI金鑰", + }, + SecretKey: { + Title: "密鑰", + SubTitle: "使用您的HUAWEI密鑰", + Placeholder: "HUAWEI密鑰", + }, + Endpoint: { + Title: "端點地址", + SubTitle: "不支援自訂的.env配置", + }, + }, }, Model: "模型 (model)", diff --git a/app/locales/vi.ts b/app/locales/vi.ts index c53baf35d16..3776f92d33d 100644 --- a/app/locales/vi.ts +++ b/app/locales/vi.ts @@ -427,6 +427,22 @@ const vi: PartialLocaleType = { SubTitle: "Thêm tùy chọn mô hình tùy chỉnh, sử dụng dấu phẩy để phân cách", }, + Huawei: { + ApiKey: { + Title: "Khóa API", + SubTitle: "Sử dụng khóa API HUAWEI của bạn", + Placeholder: "Khóa HUAWEI", + }, + SecretKey: { + Title: "Khóa bí mật", + SubTitle: "Sử dụng khóa bí mật HUAWEI của bạn", + Placeholder: "Khóa bí mật HUAWEI", + }, + Endpoint: { + Title: "Địa chỉ điểm cuối", + SubTitle: "Không hỗ trợ cấu hình .env tùy chỉnh", + }, + }, }, Model: "Mô hình (model)", diff --git a/app/store/access.ts b/app/store/access.ts index 7025a181418..34ca8a701e7 100644 --- a/app/store/access.ts +++ b/app/store/access.ts @@ -17,6 +17,7 @@ import { XAI_BASE_URL, CHATGLM_BASE_URL, SILICONFLOW_BASE_URL, + HUAWEI_BASE_URL, } from "../constant"; import { getHeaders } from "../client/api"; import { getClientConfig } from "../config/client"; @@ -55,6 +56,8 @@ const DEFAULT_XAI_URL = isApp ? XAI_BASE_URL : ApiPath.XAI; const DEFAULT_CHATGLM_URL = isApp ? CHATGLM_BASE_URL : ApiPath.ChatGLM; +const DEFAULT_HUAWEI_URL = isApp ? HUAWEI_BASE_URL : ApiPath.Huawei; + const DEFAULT_SILICONFLOW_URL = isApp ? SILICONFLOW_BASE_URL : ApiPath.SiliconFlow; @@ -131,6 +134,9 @@ const DEFAULT_ACCESS_STATE = { // siliconflow siliconflowUrl: DEFAULT_SILICONFLOW_URL, siliconflowApiKey: "", + // huawei + huaweiUrl: DEFAULT_HUAWEI_URL, + huaweiApiKey: "", // server config needCode: true, @@ -219,6 +225,9 @@ export const useAccessStore = createPersistStore( return ensure(get(), ["siliconflowApiKey"]); }, + isValidHuawei() { + return ensure(get(), ["huaweiApiKey"]); + }, isAuthorized() { this.fetch(); @@ -238,6 +247,7 @@ export const useAccessStore = createPersistStore( this.isValidXAI() || this.isValidChatGLM() || this.isValidSiliconFlow() || + this.isValidHuawei() || !this.enabledAccessControl() || (this.enabledAccessControl() && ensure(get(), ["accessCode"])) ); diff --git a/docker-compose.yml b/docker-compose.yml index 935b126a394..b82f5bd7170 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,6 +17,40 @@ services: - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY - DISABLE_FAST_LINK=$DISABLE_FAST_LINK - OPENAI_SB=$OPENAI_SB + - SILICONFLOW_API_KEY=$SILICONFLOW_API_KEY + - SILICONFLOW_URL=$SILICONFLOW_URL + - AZURE_URL=$AZURE_URL + - AZURE_API_KEY=$AZURE_API_KEY + - AZURE_API_VERSION=$AZURE_API_VERSION + - GOOGLE_URL=$GOOGLE_URL + - GTM_ID=$GTM_ID + - ANTHROPIC_URL=$ANTHROPIC_URL + - ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY + - ANTHROPIC_API_VERSION=$ANTHROPIC_API_VERSION + - BAIDU_URL=$BAIDU_URL + - BAIDU_API_KEY=$BAIDU_API_KEY + - BAIDU_SECRET_KEY=$BAIDU_SECRET_KEY + - BYTEDANCE_URL=$BYTEDANCE_URL + - BYTEDANCE_API_KEY=$BYTEDANCE_API_KEY + - ALIBABA_URL=$ALIBABA_URL + - ALIBABA_API_KEY=$ALIBABA_API_KEY + - TENCENT_URL=$TENCENT_URL + - TENCENT_SECRET_KEY=$TENCENT_SECRET_KEY + - TENCENT_SECRET_ID=$TENCENT_SECRET_ID + - MOONSHOT_URL=$MOONSHOT_URL + - MOONSHOT_API_KEY=$MOONSHOT_API_KEY + - IFLYTEK_URL=$IFLYTEK_URL + - IFLYTEK_API_KEY=$IFLYTEK_API_KEY + - IFLYTEK_API_SECRET=$IFLYTEK_API_SECRET + - DEEPSEEK_URL=$DEEPSEEK_URL + - DEEPSEEK_API_KEY=$DEEPSEEK_API_KEY + - XAI_URL=$XAI_URL + - XAI_API_KEY=$XAI_API_KEY + - CHATGLM_URL=$CHATGLM_URL + - CHATGLM_API_KEY=$CHATGLM_API_KEY + - DEFAULT_INPUT_TEMPLATE=$DEFAULT_INPUT_TEMPLATE + - HUAWEI_API_KEY=$HUAWEI_API_KEY + - HUAWEI_URL=$HUAWEI_URL chatgpt-next-web-proxy: profiles: [ "proxy" ] @@ -36,3 +70,37 @@ services: - ENABLE_BALANCE_QUERY=$ENABLE_BALANCE_QUERY - DISABLE_FAST_LINK=$DISABLE_FAST_LINK - OPENAI_SB=$OPENAI_SB + - SILICONFLOW_API_KEY=$SILICONFLOW_API_KEY + - SILICONFLOW_URL=$SILICONFLOW_URL + - AZURE_URL=$AZURE_URL + - AZURE_API_KEY=$AZURE_API_KEY + - AZURE_API_VERSION=$AZURE_API_VERSION + - GOOGLE_URL=$GOOGLE_URL + - GTM_ID=$GTM_ID + - ANTHROPIC_URL=$ANTHROPIC_URL + - ANTHROPIC_API_KEY=$ANTHROPIC_API_KEY + - ANTHROPIC_API_VERSION=$ANTHROPIC_API_VERSION + - BAIDU_URL=$BAIDU_URL + - BAIDU_API_KEY=$BAIDU_API_KEY + - BAIDU_SECRET_KEY=$BAIDU_SECRET_KEY + - BYTEDANCE_URL=$BYTEDANCE_URL + - BYTEDANCE_API_KEY=$BYTEDANCE_API_KEY + - ALIBABA_URL=$ALIBABA_URL + - ALIBABA_API_KEY=$ALIBABA_API_KEY + - TENCENT_URL=$TENCENT_URL + - TENCENT_SECRET_KEY=$TENCENT_SECRET_KEY + - TENCENT_SECRET_ID=$TENCENT_SECRET_ID + - MOONSHOT_URL=$MOONSHOT_URL + - MOONSHOT_API_KEY=$MOONSHOT_API_KEY + - IFLYTEK_URL=$IFLYTEK_URL + - IFLYTEK_API_KEY=$IFLYTEK_API_KEY + - IFLYTEK_API_SECRET=$IFLYTEK_API_SECRET + - DEEPSEEK_URL=$DEEPSEEK_URL + - DEEPSEEK_API_KEY=$DEEPSEEK_API_KEY + - XAI_URL=$XAI_URL + - XAI_API_KEY=$XAI_API_KEY + - CHATGLM_URL=$CHATGLM_URL + - CHATGLM_API_KEY=$CHATGLM_API_KEY + - DEFAULT_INPUT_TEMPLATE=$DEFAULT_INPUT_TEMPLATE + - HUAWEI_API_KEY=$HUAWEI_API_KEY + - HUAWEI_URL=$HUAWEI_URL