diff --git a/src/app/api/chat/__tests__/route.test.ts b/src/app/api/chat/__tests__/route.test.ts
index c8496d5d..4c9e7841 100644
--- a/src/app/api/chat/__tests__/route.test.ts
+++ b/src/app/api/chat/__tests__/route.test.ts
@@ -58,7 +58,7 @@ describe("/api/chat route", () => {
});
});
- it("uses NewApiWG GPT-4.1 Mini for chat requests", async () => {
+ it("uses NewApiWG Doubao Seed 2.0 Lite for chat requests", async () => {
const request = new Request("http://localhost/api/chat", {
method: "POST",
headers: {
@@ -80,7 +80,7 @@ describe("/api/chat route", () => {
apiKey: "browser-newapi-key",
baseURL: "https://llmapitest.popi.art/v1",
});
- expect(mockNewApiWGProvider).toHaveBeenCalledWith("gpt-4.1-mini");
+ expect(mockNewApiWGProvider).toHaveBeenCalledWith("doubao-seed-2-0-lite-260428");
expect(mockStreamText).toHaveBeenCalledWith(
expect.objectContaining({
model: "newapiwg-model",
diff --git a/src/components/ProjectSetupModal.tsx b/src/components/ProjectSetupModal.tsx
index 44a68a5d..6b87865c 100644
--- a/src/components/ProjectSetupModal.tsx
+++ b/src/components/ProjectSetupModal.tsx
@@ -10,8 +10,12 @@ import { ProviderModel } from "@/lib/providers/types";
import { ModelSearchDialog } from "@/components/modals/ModelSearchDialog";
import { useInlineParameters } from "@/hooks/useInlineParameters";
import { useI18n } from "@/i18n";
-import { LLM_MODELS, LLM_PROVIDERS } from "@/lib/llmModels";
+import { DEFAULT_NEWAPIWG_LLM_MODEL_ID, LLM_MODELS, LLM_PROVIDERS } from "@/lib/llmModels";
import { getProviderDisplayName, isPopiProviderMode } from "@/lib/providerMode";
+import {
+ createNewApiWGDefaultImageModel,
+ createNewApiWGDefaultVideoModel,
+} from "@/store/utils/defaultImageModel";
import {
isBrowserDirectoryPickerSupported,
isBrowserFileSystemPath,
@@ -74,6 +78,13 @@ const getProviderIcon = (provider: ProviderType) => {
};
const DEFAULT_LLM_PROVIDER: LLMProvider = "newapiwg";
+const DEFAULT_LLM_TEMPERATURE = 0.7;
+const DEFAULT_LLM_MAX_TOKENS = 8192;
+
+function getDefaultModelForLLMProvider(provider: LLMProvider): LLMModelType {
+ if (provider === DEFAULT_LLM_PROVIDER) return DEFAULT_NEWAPIWG_LLM_MODEL_ID;
+ return LLM_MODELS[provider][0].value;
+}
interface ProjectSetupModalProps {
isOpen: boolean;
@@ -414,6 +425,14 @@ export function ProjectSetupModal({
}));
};
+ const effectiveImageModel = localNodeDefaults.generateImage?.selectedModel ?? createNewApiWGDefaultImageModel();
+ const effectiveVideoModel = localNodeDefaults.generateVideo?.selectedModel ?? createNewApiWGDefaultVideoModel();
+ const effectiveLLMProvider = localNodeDefaults.llm?.provider ?? DEFAULT_LLM_PROVIDER;
+ const effectiveLLMModels = LLM_MODELS[effectiveLLMProvider] || LLM_MODELS.newapiwg;
+ const effectiveLLMModel = localNodeDefaults.llm?.model ?? getDefaultModelForLLMProvider(effectiveLLMProvider);
+ const effectiveLLMTemperature = localNodeDefaults.llm?.temperature ?? DEFAULT_LLM_TEMPERATURE;
+ const effectiveLLMMaxTokens = localNodeDefaults.llm?.maxTokens ?? DEFAULT_LLM_MAX_TOKENS;
+
if (!isOpen) return null;
return (
@@ -965,44 +984,29 @@ export function ProjectSetupModal({
{t("project.defaultLLM")}
- {localNodeDefaults.llm && (
-
- )}
+
- {!localNodeDefaults.llm ? (
-
{t("project.systemLLMDefault")}
- ) : null}
-
{/* Provider dropdown */}
diff --git a/src/i18n/index.tsx b/src/i18n/index.tsx
index a6c71b6b..188f24f2 100644
--- a/src/i18n/index.tsx
+++ b/src/i18n/index.tsx
@@ -47,6 +47,7 @@ const en = {
"common.previous": "Previous",
"common.nextItem": "Next",
"common.processing": "Processing...",
+ "error.providerOverloaded": "{model}: provider is temporarily overloaded. Please retry in a moment or switch models.",
"node.optionalTitle": "This input is optional - empty inputs will be skipped",
"node.markOptionalTitle": "Mark as optional - empty inputs will skip this branch",
"header.openWelcome": "Open welcome screen",
@@ -538,7 +539,7 @@ const en = {
"project.systemImageDefault": "System default (Popi Models)",
"project.noneSetFirstUse": "None set (select on first use)",
"project.defaultLLM": "Default LLM Settings",
- "project.systemLLMDefault": "Using system defaults (Popi Models GPT-4.1 Mini)",
+ "project.systemLLMDefault": "Using system defaults (Popi Models Doubao Seed 2.0 Lite)",
"project.provider": "Provider",
"project.model": "Model",
"project.temp": "Temp: {value}",
@@ -659,6 +660,7 @@ const zhCN: Dictionary = {
"common.previous": "上一个",
"common.nextItem": "下一个",
"common.processing": "处理中...",
+ "error.providerOverloaded": "{model}:服务商暂时过载。请稍后重试,或切换到其他模型。",
"node.optionalTitle": "此输入为可选,空输入会被跳过",
"node.markOptionalTitle": "标记为可选,空输入会跳过这个分支",
"header.openWelcome": "打开欢迎页",
@@ -1148,7 +1150,7 @@ const zhCN: Dictionary = {
"project.systemImageDefault": "系统默认(Popi Models)",
"project.noneSetFirstUse": "未设置(首次使用时选择)",
"project.defaultLLM": "默认 LLM 设置",
- "project.systemLLMDefault": "使用系统默认值(Popi Models GPT-4.1 Mini)",
+ "project.systemLLMDefault": "使用系统默认值(Popi Models Doubao Seed 2.0 Lite)",
"project.provider": "服务商",
"project.model": "模型",
"project.temp": "温度:{value}",
@@ -1283,6 +1285,7 @@ const zhTW: Dictionary = {
"common.previous": "上一個",
"common.nextItem": "下一個",
"common.processing": "處理中...",
+ "error.providerOverloaded": "{model}:服務商暫時過載。請稍後重試,或切換到其他模型。",
"node.optionalTitle": "此輸入為可選,空輸入會被略過",
"node.markOptionalTitle": "標記為可選,空輸入會略過這個分支",
"header.openWelcome": "開啟歡迎頁",
@@ -1670,7 +1673,7 @@ const zhTW: Dictionary = {
"project.newapiwgDesc": "Popi 統一模型閘道",
"project.baseUrl": "Base URL",
"project.defaultLLM": "預設 LLM 設定",
- "project.systemLLMDefault": "使用系統預設值(Popi Models GPT-4.1 Mini)",
+ "project.systemLLMDefault": "使用系統預設值(Popi Models Doubao Seed 2.0 Lite)",
"project.executionSettings": "執行設定",
"project.maxParallel": "最大平行呼叫:{count}",
"project.canvasHelp": "設定畫布導覽和互動方式。",
@@ -1746,6 +1749,7 @@ const ja: Dictionary = {
"common.previous": "前へ",
"common.nextItem": "次へ",
"common.processing": "処理中...",
+ "error.providerOverloaded": "{model}: プロバイダーが一時的に混雑しています。少し待って再試行するか、別のモデルに切り替えてください。",
"node.optionalTitle": "この入力は任意です。空の入力はスキップされます",
"node.markOptionalTitle": "任意に設定します。空の入力はこの分岐をスキップします",
"header.openWelcome": "ウェルカム画面を開く",
@@ -2181,7 +2185,7 @@ const ja: Dictionary = {
"project.systemImageDefault": "システム既定(Popi Models)",
"project.noneSetFirstUse": "未設定(初回使用時に選択)",
"project.defaultLLM": "既定の LLM 設定",
- "project.systemLLMDefault": "システム既定値を使用(Popi Models GPT-4.1 Mini)",
+ "project.systemLLMDefault": "システム既定値を使用(Popi Models Doubao Seed 2.0 Lite)",
"project.provider": "プロバイダー",
"project.model": "モデル",
"project.temp": "温度: {value}",
diff --git a/src/lib/__tests__/llmModels.test.ts b/src/lib/__tests__/llmModels.test.ts
index 7c578bac..6893ba8c 100644
--- a/src/lib/__tests__/llmModels.test.ts
+++ b/src/lib/__tests__/llmModels.test.ts
@@ -2,15 +2,11 @@ import { describe, expect, it } from "vitest";
import { DEFAULT_NEWAPIWG_LLM_MODEL_ID, LLM_MODELS } from "@/lib/llmModels";
describe("LLM model options", () => {
- it("defaults NewApiWG text generation to GPT-4.1 Mini", () => {
- expect(DEFAULT_NEWAPIWG_LLM_MODEL_ID).toBe("gpt-4.1-mini");
+ it("defaults NewApiWG text generation to Doubao Seed 2.0 Lite", () => {
+ expect(DEFAULT_NEWAPIWG_LLM_MODEL_ID).toBe("doubao-seed-2-0-lite-260428");
});
it("includes Doubao Seed 2.0 for NewApiWG selectors", () => {
- expect(LLM_MODELS.newapiwg).toContainEqual({
- value: "gpt-4.1-mini",
- label: "GPT-4.1 Mini",
- });
expect(LLM_MODELS.newapiwg).toContainEqual({
value: "doubao-seed-2-0-lite-260428",
label: "Doubao Seed 2.0 Lite",
@@ -19,5 +15,9 @@ describe("LLM model options", () => {
value: "doubao-seed-2-0-pro-260215",
label: "Doubao Seed 2.0 Pro",
});
+ expect(LLM_MODELS.newapiwg).toContainEqual({
+ value: "gpt-4.1-mini",
+ label: "GPT-4.1 Mini",
+ });
});
});
diff --git a/src/lib/chat/tools.ts b/src/lib/chat/tools.ts
index d2142285..e068a6bc 100644
--- a/src/lib/chat/tools.ts
+++ b/src/lib/chat/tools.ts
@@ -42,7 +42,7 @@ AI video generation. Takes image + text inputs, outputs video. Use Popi Models f
### LLM Text Generation
AI text generation for expanding prompts or analyzing images.
- **Provider dropdown**: Popi Models
-- **Model dropdown**: GPT-4.1 Mini (Popi Models default), Doubao Seed 2.0 Lite/Pro (Popi Models options)
+- **Model dropdown**: Doubao Seed 2.0 Lite (Popi Models default), Doubao Seed 2.0 Pro and GPT-4.1 Mini (Popi Models options)
- **Parameters** (collapsible): Temperature slider (0-2), Max Tokens slider (256-16384)
- Takes **text** input (required), optional **image** input
diff --git a/src/lib/llmModels.ts b/src/lib/llmModels.ts
index bbbe0efa..7f04f8ae 100644
--- a/src/lib/llmModels.ts
+++ b/src/lib/llmModels.ts
@@ -6,7 +6,7 @@ export interface LLMModelOption {
label: string;
}
-export const DEFAULT_NEWAPIWG_LLM_MODEL_ID: LLMModelType = "gpt-4.1-mini";
+export const DEFAULT_NEWAPIWG_LLM_MODEL_ID: LLMModelType = "doubao-seed-2-0-lite-260428";
const ALL_LLM_PROVIDERS: { value: LLMProvider; label: string }[] = [
{ value: "newapiwg", label: getProviderDisplayName("newapiwg") },
@@ -22,9 +22,9 @@ export const LLM_PROVIDERS: { value: LLMProvider; label: string }[] =
export const LLM_MODELS: Record
= {
newapiwg: [
- { value: "gpt-4.1-mini", label: "GPT-4.1 Mini" },
{ value: "doubao-seed-2-0-lite-260428", label: "Doubao Seed 2.0 Lite" },
{ value: "doubao-seed-2-0-pro-260215", label: "Doubao Seed 2.0 Pro" },
+ { value: "gpt-4.1-mini", label: "GPT-4.1 Mini" },
],
google: [
{ value: "gemini-3-flash-preview", label: "Gemini 3 Flash" },
diff --git a/src/store/execution/__tests__/generateVideoExecutor.test.ts b/src/store/execution/__tests__/generateVideoExecutor.test.ts
index 79d6479c..6567b646 100644
--- a/src/store/execution/__tests__/generateVideoExecutor.test.ts
+++ b/src/store/execution/__tests__/generateVideoExecutor.test.ts
@@ -214,8 +214,8 @@ describe("executeGenerateVideo", () => {
},
fallbackModel: {
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
- displayName: "Vidu Q2 Pro Fast",
+ modelId: "doubao-seedance-2-0-260128",
+ displayName: "doubao-seedance-2-0-260128",
},
fallbackParameters: { duration: 5 },
});
@@ -239,13 +239,13 @@ describe("executeGenerateVideo", () => {
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(body.selectedModel).toMatchObject({
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
+ modelId: "doubao-seedance-2-0-260128",
});
expect(body.parameters).toEqual({ duration: 5 });
expect(ctx.updateNodeData).toHaveBeenCalledWith("vid-1", expect.objectContaining({
selectedModel: expect.objectContaining({
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
+ modelId: "doubao-seedance-2-0-260128",
}),
parameters: { duration: 5 },
fallbackModel: undefined,
@@ -280,7 +280,7 @@ describe("executeGenerateVideo", () => {
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
expect(body.selectedModel).toMatchObject({
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
+ modelId: "doubao-seedance-2-0-260128",
});
});
@@ -315,7 +315,7 @@ describe("executeGenerateVideo", () => {
const body = JSON.parse(request[1].body);
expect(body.selectedModel).toMatchObject({
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
+ modelId: "doubao-seedance-2-0-260128",
});
expect(request[1].headers["X-NewApiWG-Key"]).toBeUndefined();
});
diff --git a/src/store/utils/__tests__/nodeDefaults.test.ts b/src/store/utils/__tests__/nodeDefaults.test.ts
index 1f4be47f..31abfe2d 100644
--- a/src/store/utils/__tests__/nodeDefaults.test.ts
+++ b/src/store/utils/__tests__/nodeDefaults.test.ts
@@ -278,8 +278,8 @@ describe("nodeDefaults utilities", () => {
expect((data as any).selectedModel).toEqual({
provider: "newapiwg",
- modelId: "viduq2-pro-fast",
- displayName: "Vidu Q2 Pro Fast",
+ modelId: "doubao-seedance-2-0-260128",
+ displayName: "doubao-seedance-2-0-260128",
capabilities: ["text-to-video", "image-to-video"],
});
});
@@ -310,7 +310,7 @@ describe("nodeDefaults utilities", () => {
const data = createDefaultNodeData("llmGenerate");
expect((data as any).provider).toBe("newapiwg");
- expect((data as any).model).toBe("gpt-4.1-mini");
+ expect((data as any).model).toBe("doubao-seed-2-0-lite-260428");
expect((data as any).temperature).toBe(0.7);
expect((data as any).maxTokens).toBe(8192);
});
@@ -332,7 +332,7 @@ describe("nodeDefaults utilities", () => {
expect((data as any).temperature).toBe(0.9);
// Should fall back to NewApiWG defaults for others
expect((data as any).provider).toBe("newapiwg");
- expect((data as any).model).toBe("gpt-4.1-mini");
+ expect((data as any).model).toBe("doubao-seed-2-0-lite-260428");
expect((data as any).maxTokens).toBe(8192);
});
});
diff --git a/src/store/utils/defaultImageModel.ts b/src/store/utils/defaultImageModel.ts
index bc8183e4..daacd8ef 100644
--- a/src/store/utils/defaultImageModel.ts
+++ b/src/store/utils/defaultImageModel.ts
@@ -2,14 +2,14 @@ import type { ModelType, ProviderEnvStatus, ProviderSettings, ProviderType, Sele
import { MODEL_DISPLAY_NAMES } from "@/types";
const NEWAPIWG_DEFAULT_IMAGE_MODEL_ID = "apiyi_nano_banana_2";
-const NEWAPIWG_DEFAULT_VIDEO_MODEL_ID = "viduq2-pro-fast";
+const NEWAPIWG_DEFAULT_VIDEO_MODEL_ID = "doubao-seedance-2-0-260128";
const DEFAULT_AUDIO_MODEL_ID = "elevenlabs/turbo-v2.5";
export function createNewApiWGDefaultImageModel(): SelectedModel {
return {
provider: "newapiwg",
modelId: NEWAPIWG_DEFAULT_IMAGE_MODEL_ID,
- displayName: "APIYI Nano Banana 2",
+ displayName: NEWAPIWG_DEFAULT_IMAGE_MODEL_ID,
capabilities: ["text-to-image", "image-to-image"],
};
}
@@ -18,7 +18,7 @@ export function createNewApiWGDefaultVideoModel(): SelectedModel {
return {
provider: "newapiwg",
modelId: NEWAPIWG_DEFAULT_VIDEO_MODEL_ID,
- displayName: "Vidu Q2 Pro Fast",
+ displayName: NEWAPIWG_DEFAULT_VIDEO_MODEL_ID,
capabilities: ["text-to-video", "image-to-video"],
};
}
diff --git a/src/utils/__tests__/userFacingErrors.test.ts b/src/utils/__tests__/userFacingErrors.test.ts
new file mode 100644
index 00000000..4cdf84a5
--- /dev/null
+++ b/src/utils/__tests__/userFacingErrors.test.ts
@@ -0,0 +1,31 @@
+import { describe, expect, it } from "vitest";
+import type { TranslationKey } from "@/i18n";
+import { formatUserFacingError } from "@/utils/userFacingErrors";
+
+const t = (key: TranslationKey, values?: Record) => {
+ if (key === "error.providerOverloaded") {
+ return `${values?.model}: localized overload`;
+ }
+ return key;
+};
+
+describe("formatUserFacingError", () => {
+ it("localizes normalized provider overload errors", () => {
+ expect(formatUserFacingError(
+ "apiyi_nano_banana_pro: provider is temporarily overloaded. Please retry in a moment or switch models.",
+ t
+ )).toBe("apiyi_nano_banana_pro: localized overload");
+ });
+
+ it("uses the currently displayed model name when provided", () => {
+ expect(formatUserFacingError(
+ "apiyi_nano_banana_pro: provider is temporarily overloaded. Please retry in a moment or switch models.",
+ t,
+ "PopiArt Nano Pro"
+ )).toBe("PopiArt Nano Pro: localized overload");
+ });
+
+ it("preserves unknown upstream errors", () => {
+ expect(formatUserFacingError("unexpected upstream failure", t)).toBe("unexpected upstream failure");
+ });
+});
diff --git a/src/utils/userFacingErrors.ts b/src/utils/userFacingErrors.ts
new file mode 100644
index 00000000..55e71b98
--- /dev/null
+++ b/src/utils/userFacingErrors.ts
@@ -0,0 +1,23 @@
+import type { TranslationKey } from "@/i18n";
+
+type Translate = (key: TranslationKey, values?: Record) => string;
+
+const PROVIDER_OVERLOADED_RE =
+ /^(.*?): provider is temporarily overloaded\. Please retry in a moment or switch models\.$/i;
+
+export function formatUserFacingError(
+ message: string | null | undefined,
+ t: Translate,
+ modelDisplayName?: string | null
+): string | null {
+ if (!message) return null;
+
+ const overloadedMatch = message.match(PROVIDER_OVERLOADED_RE);
+ if (overloadedMatch) {
+ return t("error.providerOverloaded", {
+ model: modelDisplayName?.trim() || (overloadedMatch[1] ?? "Model").trim(),
+ });
+ }
+
+ return message;
+}