diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx
index c93163bfa..d8a3c621e 100644
--- a/app/(admin)/settings/admin-settings-client.tsx
+++ b/app/(admin)/settings/admin-settings-client.tsx
@@ -771,6 +771,7 @@ type AiModel = {
isEnabled: boolean;
isHidden: boolean;
isDefault: boolean;
+ isDefaultTextEditModel?: boolean;
isSubModel?: boolean;
parentModelId?: string | null;
sortOrder: number;
@@ -3260,6 +3261,7 @@ export function AdminSettingsClient() {
isEnabled: true,
isHidden: false,
isDefault: false,
+ isDefaultTextEditModel: false,
isAgentModel: false,
agentCodingModelId: "",
isMultimodal: false,
@@ -6499,6 +6501,7 @@ export function AdminSettingsClient() {
isEnabled: true,
isHidden: false,
isDefault: false,
+ isDefaultTextEditModel: false,
isAgentModel: false,
agentCodingModelId: "",
isMultimodal: false,
@@ -14194,6 +14197,10 @@ export function AdminSettingsClient() {
setModelForm({
...modelForm,
supportsImageGen: checked,
+ isDefaultTextEditModel:
+ checked
+ ? modelForm.isDefaultTextEditModel
+ : false,
})
}
/>
@@ -14231,6 +14238,30 @@ export function AdminSettingsClient() {
+
+
+ setModelForm({
+ ...modelForm,
+ isDefaultTextEditModel: checked,
+ })
+ }
+ />
+
+
+
+ 建议给可用的火山生图模型开启。未配置时,系统仍会回退到当前可用的火山生图模型。
+
)}
+ {model.isDefaultTextEditModel && (
+
+ 文字编辑默认
+
+ )}
{model.isAgentModel && (
Agent
@@ -15040,6 +15076,8 @@ export function AdminSettingsClient() {
isEnabled: model.isEnabled,
isHidden: model.isHidden || false,
isDefault: model.isDefault,
+ isDefaultTextEditModel:
+ model.isDefaultTextEditModel || false,
isAgentModel: model.isAgentModel || false,
agentCodingModelId:
model.agentCodingModelId || "",
diff --git a/app/api/admin/ai-models/route.ts b/app/api/admin/ai-models/route.ts
index 7038e4dc1..5383f7f81 100644
--- a/app/api/admin/ai-models/route.ts
+++ b/app/api/admin/ai-models/route.ts
@@ -74,6 +74,7 @@ const createModelSchema = z.object({
isEnabled: z.boolean().optional(),
isHidden: z.boolean().optional(),
isDefault: z.boolean().optional(),
+ isDefaultTextEditModel: z.boolean().optional(),
sortOrder: z.number().optional(),
fallbackModelId1: z.string().max(128).optional(),
fallbackModelId2: z.string().max(128).optional(),
@@ -97,6 +98,7 @@ const updateModelSchema = z.object({
isEnabled: z.boolean().optional(),
isHidden: z.boolean().optional(),
isDefault: z.boolean().optional(),
+ isDefaultTextEditModel: z.boolean().optional(),
sortOrder: z.number().optional(),
fallbackModelId1: z.string().max(128).optional(),
fallbackModelId2: z.string().max(128).optional(),
diff --git a/app/api/canvas/text-edit/route.ts b/app/api/canvas/text-edit/route.ts
index 311e27843..1ffb68119 100644
--- a/app/api/canvas/text-edit/route.ts
+++ b/app/api/canvas/text-edit/route.ts
@@ -19,15 +19,17 @@ const requestSchema = z.object({
items: z.array(textItemSchema).min(1, "至少需要一条文字修改"),
});
-function pickVolcanoModel(models: Awaited>) {
+function pickTextEditModel(models: Awaited>) {
const candidates = models
.filter((item) => item.model.supportsImageGen && item.provider.type === "volcano-vision")
.sort((a, b) => {
const aScore =
+ (a.model.isDefaultTextEditModel ? 100 : 0) +
(a.model.isDefault ? 10 : 0) +
(a.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
(a.model.displayName?.includes("火山") ? 3 : 0);
const bScore =
+ (b.model.isDefaultTextEditModel ? 100 : 0) +
(b.model.isDefault ? 10 : 0) +
(b.model.modelId?.toLowerCase().includes("seedream") ? 5 : 0) +
(b.model.displayName?.includes("火山") ? 3 : 0);
@@ -148,12 +150,27 @@ export async function POST(request: Request) {
}
const models = await getEnabledAiModelsWithProvider();
- const selected = pickVolcanoModel(models);
+ const selected = pickTextEditModel(models);
if (!selected) {
- return NextResponse.json({ success: false, error: "未配置可用的火山生图模型" }, { status: 500 });
+ return NextResponse.json(
+ {
+ success: false,
+ error: "未配置可用的文字编辑生图模型,请在系统设置-AI 模型中启用一个火山生图模型并设为默认文字编辑生图模型",
+ },
+ { status: 500 },
+ );
}
+ console.info("[Canvas Text Edit] selected model:", {
+ providerType: selected.provider.type,
+ providerName: selected.provider.name,
+ modelId: selected.model.modelId,
+ displayName: selected.model.displayName,
+ isDefault: selected.model.isDefault,
+ isDefaultTextEditModel: selected.model.isDefaultTextEditModel,
+ });
+
const provider = createVolcanoVision({
baseURL: selected.provider.baseUrl,
apiKey: selected.provider.apiKey,
diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts
index b5b445b7e..3aa4d011b 100644
--- a/lib/db/migrate.ts
+++ b/lib/db/migrate.ts
@@ -105,6 +105,10 @@ const runMigrate = async () => {
ALTER TABLE "AiModel"
ADD COLUMN IF NOT EXISTS "excludeFromFailover" boolean NOT NULL DEFAULT false;
`);
+ await db.execute(sql`
+ ALTER TABLE "AiModel"
+ ADD COLUMN IF NOT EXISTS "isDefaultTextEditModel" boolean NOT NULL DEFAULT false;
+ `);
for (const item of providerBaseUrlDefaults) {
await db.execute(sql`
UPDATE "AiProvider"
diff --git a/lib/db/migrations/0076_add_ai_model_text_edit_default.sql b/lib/db/migrations/0076_add_ai_model_text_edit_default.sql
new file mode 100644
index 000000000..7372a9020
--- /dev/null
+++ b/lib/db/migrations/0076_add_ai_model_text_edit_default.sql
@@ -0,0 +1,2 @@
+ALTER TABLE "AiModel"
+ADD COLUMN IF NOT EXISTS "isDefaultTextEditModel" boolean NOT NULL DEFAULT false;
diff --git a/lib/db/migrations/meta/_journal.json b/lib/db/migrations/meta/_journal.json
index 0e291c8e9..cc74ac8df 100644
--- a/lib/db/migrations/meta/_journal.json
+++ b/lib/db/migrations/meta/_journal.json
@@ -435,6 +435,13 @@
"when": 1777276800000,
"tag": "0075_add_agent_workflow_default_model",
"breakpoints": true
+ },
+ {
+ "idx": 62,
+ "version": "7",
+ "when": 1777305600000,
+ "tag": "0076_add_ai_model_text_edit_default",
+ "breakpoints": true
}
]
}
diff --git a/lib/db/queries.ts b/lib/db/queries.ts
index 01e70f462..0ec95b293 100644
--- a/lib/db/queries.ts
+++ b/lib/db/queries.ts
@@ -3941,6 +3941,7 @@ export async function createAiModel({
isEnabled,
isHidden,
isDefault,
+ isDefaultTextEditModel,
sortOrder,
fallbackModelId1,
fallbackModelId2,
@@ -3961,6 +3962,7 @@ export async function createAiModel({
isEnabled?: boolean;
isHidden?: boolean;
isDefault?: boolean;
+ isDefaultTextEditModel?: boolean;
sortOrder?: number;
fallbackModelId1?: string;
fallbackModelId2?: string;
@@ -3980,6 +3982,13 @@ export async function createAiModel({
.where(eq(aiModel.isDefault, true));
}
+ if (isDefaultTextEditModel) {
+ await db
+ .update(aiModel)
+ .set({ isDefaultTextEditModel: false })
+ .where(eq(aiModel.isDefaultTextEditModel, true));
+ }
+
const defaultParams =
agentCodingModelId !== undefined
? withAgentCodingModelId(
@@ -4003,6 +4012,7 @@ export async function createAiModel({
isEnabled: isEnabled ?? true,
isHidden: isHidden ?? false,
isDefault: isDefault ?? false,
+ isDefaultTextEditModel: isDefaultTextEditModel ?? false,
sortOrder: sortOrder ?? 0,
fallbackModelId1: fallbackModelId1 || null,
fallbackModelId2: fallbackModelId2 || null,
@@ -4035,6 +4045,7 @@ export async function updateAiModel({
isEnabled,
isHidden,
isDefault,
+ isDefaultTextEditModel,
sortOrder,
fallbackModelId1,
fallbackModelId2,
@@ -4056,6 +4067,7 @@ export async function updateAiModel({
isEnabled?: boolean;
isHidden?: boolean;
isDefault?: boolean;
+ isDefaultTextEditModel?: boolean;
sortOrder?: number;
fallbackModelId1?: string;
fallbackModelId2?: string;
@@ -4075,6 +4087,13 @@ export async function updateAiModel({
.where(eq(aiModel.isDefault, true));
}
+ if (isDefaultTextEditModel) {
+ await db
+ .update(aiModel)
+ .set({ isDefaultTextEditModel: false })
+ .where(eq(aiModel.isDefaultTextEditModel, true));
+ }
+
const updateData: any = { updatedAt: new Date() };
if (providerId !== undefined) updateData.providerId = providerId;
if (modelId !== undefined) updateData.modelId = modelId;
@@ -4087,6 +4106,9 @@ export async function updateAiModel({
if (isEnabled !== undefined) updateData.isEnabled = isEnabled;
if (isHidden !== undefined) updateData.isHidden = isHidden;
if (isDefault !== undefined) updateData.isDefault = isDefault;
+ if (isDefaultTextEditModel !== undefined) {
+ updateData.isDefaultTextEditModel = isDefaultTextEditModel;
+ }
if (sortOrder !== undefined) updateData.sortOrder = sortOrder;
// 备用模型 ID(空字符串转为 null)
if (fallbackModelId1 !== undefined) updateData.fallbackModelId1 = fallbackModelId1 || null;
diff --git a/lib/db/schema.ts b/lib/db/schema.ts
index a750be413..06ff7fc51 100644
--- a/lib/db/schema.ts
+++ b/lib/db/schema.ts
@@ -304,6 +304,7 @@ export const aiModel = pgTable("AiModel", {
isEnabled: boolean("isEnabled").notNull().default(true), // 是否启用
isHidden: boolean("isHidden").notNull().default(false), // 是否在前端模型选择中隐藏
isDefault: boolean("isDefault").notNull().default(false), // 是否为默认模型
+ isDefaultTextEditModel: boolean("isDefaultTextEditModel").notNull().default(false), // 是否为默认文字编辑生图模型
sortOrder: integer("sortOrder").notNull().default(0), // 排序顺序
capabilities: jsonb("capabilities"), // 🆕 模型能力配置(用于视频模型)
defaultParams: jsonb("defaultParams"), // 🆕 默认参数配置