+
- 快捷编辑
- Tab
+ 快捷编辑
+ Tab
@@ -2080,7 +2082,7 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
onClick={() => handleAction('enhance')}
>
HD
- 放大
+ 高清增强
-
-
@@ -2156,7 +2148,11 @@ export function ImageNode({ node, isSelected }: ImageNodeProps) {
-
+
handleAction('extend')}>
扩图
diff --git a/lib/tripo/client.ts b/lib/tripo/client.ts
index 547d2acb2..7a1531131 100644
--- a/lib/tripo/client.ts
+++ b/lib/tripo/client.ts
@@ -42,6 +42,20 @@ function normalizeBaseUrl(baseUrl?: string) {
return resolved.replace(/\/+$/, "");
}
+function sanitizeHeaderText(value: string): string {
+ return String(value || "")
+ .replace(/[\u2028\u2029]/g, "")
+ .replace(/[\u0000-\u001F\u007F]/g, "")
+ .trim();
+}
+
+function hasNonByteStringChar(value: string): boolean {
+ for (let i = 0; i < value.length; i += 1) {
+ if (value.charCodeAt(i) > 255) return true;
+ }
+ return false;
+}
+
async function parseResponseBody(response: Response): Promise {
const contentType = response.headers.get("content-type") || "";
if (contentType.includes("application/json")) {
@@ -59,7 +73,23 @@ export async function tripoRequest(
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
const url = `${normalizeBaseUrl(config.baseUrl)}${path}`;
const headers = new Headers(options.headers || {});
- headers.set("Authorization", `Bearer ${config.apiKey}`);
+
+ const normalizedApiKey = sanitizeHeaderText(String(config.apiKey || "")).replace(/\s+/g, "");
+ if (!normalizedApiKey) {
+ throw new TripoApiError({
+ message: "Tripo3D API Key 为空,请在本地AI设置中重新保存",
+ httpStatus: 400,
+ });
+ }
+ if (hasNonByteStringChar(normalizedApiKey)) {
+ throw new TripoApiError({
+ message:
+ "Tripo3D API Key 含非法字符(可能是复制时带入的隐藏换行符),请在本地AI设置中重新粘贴保存",
+ httpStatus: 400,
+ });
+ }
+
+ headers.set("Authorization", `Bearer ${normalizedApiKey}`);
headers.set("Accept", "application/json");
let body: BodyInit | undefined;
diff --git a/lib/tripo/server.ts b/lib/tripo/server.ts
index f1973ad1a..0c2293401 100644
--- a/lib/tripo/server.ts
+++ b/lib/tripo/server.ts
@@ -5,27 +5,42 @@ import type { TripoProviderConfig } from "./types";
const DEFAULT_TRIPO_BASE_URL = "https://api.tripo3d.ai/v2/openapi";
+function sanitizeTripoApiKey(value: string): string {
+ return String(value || "")
+ .replace(/[\u2028\u2029]/g, "")
+ .replace(/[\u0000-\u001F\u007F]/g, "")
+ .replace(/\s+/g, "")
+ .trim();
+}
+
+function sanitizeTripoBaseUrl(value: string): string {
+ return String(value || "")
+ .replace(/[\u2028\u2029]/g, "")
+ .replace(/[\u0000-\u001F\u007F]/g, "")
+ .trim();
+}
+
export async function getTripoProviderConfig(): Promise {
const providers = await getEnabledAiProviders();
const tripoProvider = providers.find((item) => item.type === "tripo-3d");
- if (tripoProvider?.apiKey?.trim()) {
+ const providerApiKey = sanitizeTripoApiKey(String(tripoProvider?.apiKey || ""));
+ if (tripoProvider && providerApiKey) {
return {
- apiKey: tripoProvider.apiKey.trim(),
- baseUrl: String(tripoProvider.baseUrl || "").trim() || DEFAULT_TRIPO_BASE_URL,
+ apiKey: providerApiKey,
+ baseUrl: sanitizeTripoBaseUrl(String(tripoProvider?.baseUrl || "")) || DEFAULT_TRIPO_BASE_URL,
providerId: tripoProvider.id,
providerName: tripoProvider.name,
};
}
- const envApiKey = String(process.env.TRIPO_API_KEY || "").trim();
+ const envApiKey = sanitizeTripoApiKey(String(process.env.TRIPO_API_KEY || ""));
if (!envApiKey) {
throw new Error("未配置 Tripo3D API,请在系统设置-本地AI新增 Tripo3D 提供商");
}
return {
apiKey: envApiKey,
- baseUrl: String(process.env.TRIPO_BASE_URL || "").trim() || DEFAULT_TRIPO_BASE_URL,
+ baseUrl: sanitizeTripoBaseUrl(String(process.env.TRIPO_BASE_URL || "")) || DEFAULT_TRIPO_BASE_URL,
};
}
-