diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx
index 019004933..dcc5c12db 100644
--- a/app/(admin)/settings/admin-settings-client.tsx
+++ b/app/(admin)/settings/admin-settings-client.tsx
@@ -1790,6 +1790,7 @@ export function AdminSettingsClient() {
const [worldlabsKeyPoolConfig, setWorldlabsKeyPoolConfig] = useState(
defaultWorldLabsKeyPoolConfig
);
+ const [worldlabsApiKeysDraft, setWorldlabsApiKeysDraft] = useState("");
const [threeDModelAliasConfig, setThreeDModelAliasConfig] = useState(
defaultThreeDModelAliasConfig()
);
@@ -5091,6 +5092,102 @@ export function AdminSettingsClient() {
}
};
+ const handleAddWorldlabs3dPoolKey = async () => {
+ try {
+ const trimmedName = worldlabs3dConfig.name.trim() || "World Labs Key";
+ const trimmedBaseUrl =
+ worldlabs3dConfig.baseUrl.trim() ||
+ getProviderDefaultBaseUrl("worldlabs-3d");
+ const trimmedApiKey = worldlabs3dConfig.apiKey.trim();
+
+ if (!trimmedApiKey) {
+ toast.error("请填写要加入 Key 池的 World Labs API Key");
+ return;
+ }
+
+ const response = await fetch("/api/worldlabs/key-pool/import", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ keys: [
+ {
+ apiKey: trimmedApiKey,
+ name: trimmedName,
+ baseUrl: trimmedBaseUrl,
+ isEnabled: worldlabs3dConfig.isEnabled,
+ },
+ ],
+ }),
+ });
+
+ const payload = await response.json().catch(() => ({}));
+ if (!response.ok || payload?.success === false) {
+ throw new Error(
+ String(payload?.error || payload?.message || "添加 World Labs Key 失败")
+ );
+ }
+
+ const importedCount = Number(payload?.data?.importedCount || 0);
+ const skippedCount = Number(payload?.data?.skippedCount || 0);
+ toast.success(
+ importedCount > 0
+ ? "World Labs Key 已加入池"
+ : skippedCount > 0
+ ? "这个 World Labs Key 已存在,已跳过重复添加"
+ : "World Labs Key 处理完成"
+ );
+ setWorldlabs3dConfig((prev) => ({ ...prev, apiKey: "" }));
+ mutateProviders();
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : "添加 World Labs Key 失败"
+ );
+ }
+ };
+
+ const handleImportWorldlabs3dPoolKeys = async () => {
+ try {
+ const apiKeysText = worldlabsApiKeysDraft.trim();
+ if (!apiKeysText) {
+ toast.error("请先粘贴要批量导入的 World Labs API Key");
+ return;
+ }
+
+ const response = await fetch("/api/worldlabs/key-pool/import", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ apiKeysText,
+ namePrefix: worldlabs3dConfig.name.trim() || "WorldLabsKey",
+ baseUrl:
+ worldlabs3dConfig.baseUrl.trim() ||
+ getProviderDefaultBaseUrl("worldlabs-3d"),
+ isEnabled: worldlabs3dConfig.isEnabled,
+ }),
+ });
+
+ const payload = await response.json().catch(() => ({}));
+ if (!response.ok || payload?.success === false) {
+ throw new Error(
+ String(payload?.error || payload?.message || "批量导入 World Labs Key 失败")
+ );
+ }
+
+ const importedCount = Number(payload?.data?.importedCount || 0);
+ const skippedCount = Number(payload?.data?.skippedCount || 0);
+ toast.success(
+ `World Labs Key 导入完成:新增 ${importedCount} 个,跳过 ${skippedCount} 个`
+ );
+ setWorldlabsApiKeysDraft("");
+ setWorldlabs3dConfig((prev) => ({ ...prev, apiKey: "" }));
+ mutateProviders();
+ } catch (error) {
+ toast.error(
+ error instanceof Error ? error.message : "批量导入 World Labs Key 失败"
+ );
+ }
+ };
+
const updateThreeDModelAliasEntries = (
key: "tripoModels" | "worldModels",
updater: (entries: ThreeDModelAliasEntry[]) => ThreeDModelAliasEntry[]
@@ -15071,9 +15168,37 @@ export function AdminSettingsClient() {
/api/worldlabs/key-pool/import
- 批量导入新 Key,保存到数据库系统设置。
+ 批量导入新 Key;后台管理员在本页面导入时不需要额外填写令牌。
+
+
+
+
+
+ 每行一个 World Labs API Key,导入时会自动跳过已存在或重复的
+ Key。
+
+
+
+
+
@@ -15191,9 +15316,14 @@ export function AdminSettingsClient() {
-
+
+
+
+
diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts
index aabde3bed..40020f829 100644
--- a/app/(chat)/api/chat/route.ts
+++ b/app/(chat)/api/chat/route.ts
@@ -2592,6 +2592,20 @@ function resolveModelDisplayNameByModelId(
return String(match?.model?.displayName || "");
}
+function resolveModelRefByRuntimeModelId(
+ models: Awaited
> | undefined,
+ modelId: string | undefined,
+ fallbackRef?: string,
+): string {
+ const normalizedModelId = String(modelId || "").trim();
+ if (!normalizedModelId) return fallbackRef || "";
+
+ const match = models?.find(
+ (item) => item.model.modelId === normalizedModelId || item.model.id === normalizedModelId,
+ );
+ return String(match?.model?.id || fallbackRef || normalizedModelId || "");
+}
+
/**
* 解析生图 Markdown 并保存图片记录到数据库
* 支持三种格式:thumb|||url|||size、thumb$$url$$size、thumb|url|size
@@ -9403,7 +9417,11 @@ export async function POST(request: Request) {
id,
messageId,
userPrompt,
- finalModelId,
+ resolveModelRefByRuntimeModelId(
+ allModels,
+ usedModelId,
+ usedModelId === actualModelId ? finalModelId : undefined,
+ ),
{
sourceAssetIds: requestTargetResolution?.targetAssetIds,
sourceImageUrls: requestTargetResolution?.targetImageUrls,
@@ -9987,7 +10005,11 @@ export async function POST(request: Request) {
id,
messageId,
userPrompt,
- finalModelId,
+ resolveModelRefByRuntimeModelId(
+ allModels,
+ usedModelId,
+ usedModelId === actualModelId ? finalModelId : undefined,
+ ),
{
sourceAssetIds: requestTargetResolution?.targetAssetIds,
sourceImageUrls: requestTargetResolution?.targetImageUrls,
@@ -10384,7 +10406,11 @@ export async function POST(request: Request) {
id,
messageId,
userPrompt,
- finalModelId,
+ resolveModelRefByRuntimeModelId(
+ allModels,
+ usedModelId,
+ usedModelId === actualModelId ? finalModelId : undefined,
+ ),
{
sourceAssetIds: requestTargetResolution?.targetAssetIds,
sourceImageUrls: requestTargetResolution?.targetImageUrls,
@@ -11243,7 +11269,11 @@ export async function POST(request: Request) {
id,
messageId,
userPrompt,
- finalModelId,
+ resolveModelRefByRuntimeModelId(
+ allModels,
+ usedModelId,
+ usedModelId === actualModelId ? finalModelId : undefined,
+ ),
{
sourceAssetIds: requestTargetResolution?.targetAssetIds,
sourceImageUrls: requestTargetResolution?.targetImageUrls,
diff --git a/app/api/worldlabs/key-pool/import/route.ts b/app/api/worldlabs/key-pool/import/route.ts
index 2ec9205ed..8e8835032 100644
--- a/app/api/worldlabs/key-pool/import/route.ts
+++ b/app/api/worldlabs/key-pool/import/route.ts
@@ -4,6 +4,7 @@ import {
getAiProvidersByType,
} from "@/lib/db/queries";
import { ChatSDKError } from "@/lib/errors";
+import { requireAdmin } from "@/lib/middleware/admin";
import {
sanitizeWorldLabsApiKey,
sanitizeWorldLabsBaseUrl,
@@ -108,23 +109,34 @@ export async function POST(request: Request) {
try {
const dbConfig = await getWorldLabsKeyPoolConfig();
const configuredToken = dbConfig.importToken || getConfiguredImportToken();
- if (!configuredToken) {
- return Response.json(
- {
- success: false,
- error:
- "服务端未配置 World Labs key 池导入令牌,无法使用专用导入接口",
- },
- { status: 503 }
- );
+ const requestToken = readImportToken(request);
+
+ let authorizedByAdmin = false;
+ try {
+ await requireAdmin();
+ authorizedByAdmin = true;
+ } catch (_error) {
+ authorizedByAdmin = false;
}
- const requestToken = readImportToken(request);
- if (!requestToken || requestToken !== configuredToken) {
- return Response.json(
- { success: false, error: "导入令牌无效" },
- { status: 401 }
- );
+ if (!authorizedByAdmin) {
+ if (!configuredToken) {
+ return Response.json(
+ {
+ success: false,
+ error:
+ "服务端未配置 World Labs key 池导入令牌,无法使用专用导入接口",
+ },
+ { status: 503 }
+ );
+ }
+
+ if (!requestToken || requestToken !== configuredToken) {
+ return Response.json(
+ { success: false, error: "导入令牌无效" },
+ { status: 401 }
+ );
+ }
}
const rawBody = await request.json().catch(() => ({}));
diff --git a/lib/worldlabs/server.ts b/lib/worldlabs/server.ts
index ebf7a8748..753f94615 100644
--- a/lib/worldlabs/server.ts
+++ b/lib/worldlabs/server.ts
@@ -7,6 +7,7 @@ import {
import type { WorldLabsProviderConfig } from "./types";
const DEFAULT_WORLDLABS_BASE_URL = "https://api.worldlabs.ai";
+let worldLabsProviderCursor = 0;
export function sanitizeWorldLabsApiKey(value: string): string {
return String(value || "")
@@ -73,9 +74,21 @@ export async function getWorldLabsProviderConfig(
): Promise {
const providers = await getWorldLabsProviderConfigs();
const preferredId = String(preferredProviderId || "").trim();
- const worldLabsProvider = preferredId
- ? providers.find((item) => item.providerId === preferredId) || null
- : providers[0] || null;
+ if (preferredId) {
+ const matchedProvider =
+ providers.find((item) => item.providerId === preferredId) || null;
+ if (matchedProvider) {
+ return matchedProvider;
+ }
+ }
+
+ const worldLabsProvider =
+ providers.length > 0
+ ? providers[worldLabsProviderCursor % providers.length]
+ : null;
+ if (providers.length > 0) {
+ worldLabsProviderCursor = (worldLabsProviderCursor + 1) % providers.length;
+ }
if (worldLabsProvider) {
return worldLabsProvider;