diff --git a/app/api/tencent/[...path]/route.ts b/app/api/tencent/[...path]/route.ts index 8a292d432..216f941b6 100644 --- a/app/api/tencent/[...path]/route.ts +++ b/app/api/tencent/[...path]/route.ts @@ -1,4 +1,3 @@ -"use server"; import { getServerSideConfig } from "@/app/config/server"; import { TENCENT_BASE_URL, @@ -11,7 +10,7 @@ import { prettyObject } from "@/app/utils/format"; import { NextRequest, NextResponse } from "next/server"; import { auth } from "@/app/api/auth"; import { isModelAvailableInServer } from "@/app/utils/model"; -import * as crypto from "node:crypto"; +import { getHeader } from "@/app/utils/tencent"; const serverConfig = getServerSideConfig(); @@ -44,6 +43,27 @@ async function handle( export const GET = handle; export const POST = handle; +export const runtime = "nodejs"; +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(); @@ -76,10 +96,13 @@ async function request(req: NextRequest) { const fetchUrl = `${baseUrl}${path}`; const body = await req.text(); + const headers = await getHeader( + body, + serverConfig.tencentSecretId as string, + serverConfig.tencentSecretKey as string, + ); const fetchOptions: RequestInit = { - headers: { - ...getHeader(body), - }, + headers, method: req.method, body, redirect: "manual", @@ -106,107 +129,3 @@ async function request(req: NextRequest) { clearTimeout(timeoutId); } } - -// 使用 SHA-256 和 secret 进行 HMAC 加密 -function sha256(message: any, secret = "", encoding?: string) { - return crypto.createHmac("sha256", secret).update(message).digest(encoding); -} - -// 使用 SHA-256 进行哈希 -function getHash(message: any, encoding = "hex") { - return crypto.createHash("sha256").update(message).digest(encoding); -} - -function getDate(timestamp: number) { - const date = new Date(timestamp * 1000); - const year = date.getUTCFullYear(); - const month = ("0" + (date.getUTCMonth() + 1)).slice(-2); - const day = ("0" + date.getUTCDate()).slice(-2); - return `${year}-${month}-${day}`; -} - -function getHeader(payload: any) { - // https://cloud.tencent.com/document/api/1729/105701 - // 密钥参数 - const SECRET_ID = serverConfig.tencentSecretId; - const SECRET_KEY = serverConfig.tencentSecretKey; - - const endpoint = "hunyuan.tencentcloudapi.com"; - const service = "hunyuan"; - const region = ""; // optional - const action = "ChatCompletions"; - const version = "2023-09-01"; - const timestamp = Math.floor(Date.now() / 1000); - //时间处理, 获取世界时间日期 - const date = getDate(timestamp); - - // ************* 步骤 1:拼接规范请求串 ************* - - const hashedRequestPayload = getHash(payload); - const httpRequestMethod = "POST"; - const contentType = "application/json"; - const canonicalUri = "/"; - const canonicalQueryString = ""; - const canonicalHeaders = - `content-type:${contentType}\n` + - "host:" + - endpoint + - "\n" + - "x-tc-action:" + - action.toLowerCase() + - "\n"; - const signedHeaders = "content-type;host;x-tc-action"; - - const canonicalRequest = [ - httpRequestMethod, - canonicalUri, - canonicalQueryString, - canonicalHeaders, - signedHeaders, - hashedRequestPayload, - ].join("\n"); - - // ************* 步骤 2:拼接待签名字符串 ************* - const algorithm = "TC3-HMAC-SHA256"; - const hashedCanonicalRequest = getHash(canonicalRequest); - const credentialScope = date + "/" + service + "/" + "tc3_request"; - const stringToSign = - algorithm + - "\n" + - timestamp + - "\n" + - credentialScope + - "\n" + - hashedCanonicalRequest; - - // ************* 步骤 3:计算签名 ************* - const kDate = sha256(date, "TC3" + SECRET_KEY); - const kService = sha256(service, kDate); - const kSigning = sha256("tc3_request", kService); - const signature = sha256(stringToSign, kSigning, "hex"); - - // ************* 步骤 4:拼接 Authorization ************* - const authorization = - algorithm + - " " + - "Credential=" + - SECRET_ID + - "/" + - credentialScope + - ", " + - "SignedHeaders=" + - signedHeaders + - ", " + - "Signature=" + - signature; - - return { - Authorization: authorization, - "Content-Type": contentType, - Host: endpoint, - "X-TC-Action": action, - "X-TC-Timestamp": timestamp.toString(), - "X-TC-Version": version, - "X-TC-Region": region, - }; -} diff --git a/app/client/platforms/tencent.ts b/app/client/platforms/tencent.ts index 82ecd3164..119006770 100644 --- a/app/client/platforms/tencent.ts +++ b/app/client/platforms/tencent.ts @@ -22,9 +22,13 @@ import { import { prettyObject } from "@/app/utils/format"; import { getClientConfig } from "@/app/config/client"; import { getMessageTextContent, isVisionModel } from "@/app/utils"; +// @ts-ignore import mapKeys from "lodash-es/mapKeys"; +// @ts-ignore import mapValues from "lodash-es/mapValues"; +// @ts-ignore import isArray from "lodash-es/isArray"; +// @ts-ignore import isObject from "lodash-es/isObject"; export interface OpenAIListModelResponse { diff --git a/app/components/settings.tsx b/app/components/settings.tsx index 1a8c43c06..319781225 100644 --- a/app/components/settings.tsx +++ b/app/components/settings.tsx @@ -989,12 +989,12 @@ export function Settings() { subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle} > { accessStore.update( - (access) => (access.tencentApiKey = e.currentTarget.value), + (access) => (access.tencentSecretId = e.currentTarget.value), ); }} /> diff --git a/app/utils/tencent.ts b/app/utils/tencent.ts new file mode 100644 index 000000000..d304c6284 --- /dev/null +++ b/app/utils/tencent.ts @@ -0,0 +1,112 @@ +"use server"; +import * as crypto from "node:crypto"; +// 使用 SHA-256 和 secret 进行 HMAC 加密 +function sha256(message: any, secret = "", encoding?: string) { + return crypto + .createHmac("sha256", secret) + .update(message) + .digest(encoding as any); +} + +// 使用 SHA-256 进行哈希 +function getHash(message: any, encoding = "hex") { + return crypto + .createHash("sha256") + .update(message) + .digest(encoding as any); +} + +function getDate(timestamp: number) { + const date = new Date(timestamp * 1000); + const year = date.getUTCFullYear(); + const month = ("0" + (date.getUTCMonth() + 1)).slice(-2); + const day = ("0" + date.getUTCDate()).slice(-2); + return `${year}-${month}-${day}`; +} + +export async function getHeader( + payload: any, + SECRET_ID: string, + SECRET_KEY: string, +) { + // https://cloud.tencent.com/document/api/1729/105701 + + const endpoint = "hunyuan.tencentcloudapi.com"; + const service = "hunyuan"; + const region = ""; // optional + const action = "ChatCompletions"; + const version = "2023-09-01"; + const timestamp = Math.floor(Date.now() / 1000); + //时间处理, 获取世界时间日期 + const date = getDate(timestamp); + + // ************* 步骤 1:拼接规范请求串 ************* + + const hashedRequestPayload = getHash(payload); + const httpRequestMethod = "POST"; + const contentType = "application/json"; + const canonicalUri = "/"; + const canonicalQueryString = ""; + const canonicalHeaders = + `content-type:${contentType}\n` + + "host:" + + endpoint + + "\n" + + "x-tc-action:" + + action.toLowerCase() + + "\n"; + const signedHeaders = "content-type;host;x-tc-action"; + + const canonicalRequest = [ + httpRequestMethod, + canonicalUri, + canonicalQueryString, + canonicalHeaders, + signedHeaders, + hashedRequestPayload, + ].join("\n"); + + // ************* 步骤 2:拼接待签名字符串 ************* + const algorithm = "TC3-HMAC-SHA256"; + const hashedCanonicalRequest = getHash(canonicalRequest); + const credentialScope = date + "/" + service + "/" + "tc3_request"; + const stringToSign = + algorithm + + "\n" + + timestamp + + "\n" + + credentialScope + + "\n" + + hashedCanonicalRequest; + + // ************* 步骤 3:计算签名 ************* + const kDate = sha256(date, "TC3" + SECRET_KEY); + const kService = sha256(service, kDate); + const kSigning = sha256("tc3_request", kService); + const signature = sha256(stringToSign, kSigning, "hex"); + + // ************* 步骤 4:拼接 Authorization ************* + const authorization = + algorithm + + " " + + "Credential=" + + SECRET_ID + + "/" + + credentialScope + + ", " + + "SignedHeaders=" + + signedHeaders + + ", " + + "Signature=" + + signature; + + return { + Authorization: authorization, + "Content-Type": contentType, + Host: endpoint, + "X-TC-Action": action, + "X-TC-Timestamp": timestamp.toString(), + "X-TC-Version": version, + "X-TC-Region": region, + }; +}