lloydzhou 1 năm trước cách đây
mục cha
commit
e1d6131f13

+ 28 - 109
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,
-  };
-}

+ 4 - 0
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 {

+ 2 - 2
app/components/settings.tsx

@@ -989,12 +989,12 @@ export function Settings() {
         subTitle={Locale.Settings.Access.Tencent.ApiKey.SubTitle}
       >
         <PasswordInput
-          value={accessStore.tencentApiKey}
+          value={accessStore.tencentSecretId}
           type="text"
           placeholder={Locale.Settings.Access.Tencent.ApiKey.Placeholder}
           onChange={(e) => {
             accessStore.update(
-              (access) => (access.tencentApiKey = e.currentTarget.value),
+              (access) => (access.tencentSecretId = e.currentTarget.value),
             );
           }}
         />

+ 112 - 0
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,
+  };
+}