diff --git a/app/(admin)/settings/admin-settings-client.tsx b/app/(admin)/settings/admin-settings-client.tsx index 8fddac0c6..39676496f 100644 --- a/app/(admin)/settings/admin-settings-client.tsx +++ b/app/(admin)/settings/admin-settings-client.tsx @@ -98,7 +98,7 @@ type ToolConfig = { type AiProvider = { id: string; name: string; - type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image"; + type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu"; baseUrl: string; apiKey: string; isEnabled: boolean; @@ -128,6 +128,7 @@ type AiModel = { isEnabled: boolean; isDefault: boolean; sortOrder: number; + isAgentModel?: boolean; imageGenConfig?: ImageGenConfig; // 🆕 图片生成特性配置 }; @@ -284,6 +285,7 @@ const PROVIDER_TYPES = [ { value: "qianwen-video", label: "千问视频生成" }, { value: "volcano-vision", label: "火山引擎视觉" }, { value: "volcano-video", label: "火山引擎视频" }, + { value: "zhipu", label: "智普官方 4.7" }, ]; export function AdminSettingsClient() { @@ -740,7 +742,7 @@ export function AdminSettingsClient() { const [providerForm, setProviderForm] = useState({ id: "", name: "", - type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image", + type: "openai" as "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu", baseUrl: "", apiKey: "", isEnabled: true, @@ -759,6 +761,7 @@ export function AdminSettingsClient() { supportsImageGen: false, isEnabled: true, isDefault: false, + isAgentModel: false, sortOrder: 0, fallbackModelId1: "", // 备用模型 ID 1 fallbackModelId2: "", // 备用模型 ID 2 @@ -1353,6 +1356,7 @@ export function AdminSettingsClient() { supportsImageGen: false, isEnabled: true, isDefault: false, + isAgentModel: false, sortOrder: 0, fallbackModelId1: "", fallbackModelId2: "", @@ -2307,6 +2311,7 @@ export function AdminSettingsClient() { "alibaba-image": "bg-amber-500", "ruxa-image": "bg-emerald-500", "openai": "bg-green-500", + "zhipu": "bg-lime-500", "qianwen-video": "bg-cyan-500", "custom": "bg-gray-500", "local": "bg-yellow-500", @@ -3617,6 +3622,8 @@ export function AdminSettingsClient() { baseUrl: !prev.baseUrl && value === "ruxa-image" ? "https://api.ruxa.ai" + : !prev.baseUrl && value === "zhipu" + ? "https://open.bigmodel.cn/api/paas/v4" : prev.baseUrl, })) } @@ -3634,7 +3641,13 @@ export function AdminSettingsClient() { setProviderForm({ ...providerForm, baseUrl: e.target.value })} - placeholder={providerForm.type === "ruxa-image" ? "https://api.ruxa.ai" : "https://api.openai.com/v1"} + placeholder={ + providerForm.type === "ruxa-image" + ? "https://api.ruxa.ai" + : providerForm.type === "zhipu" + ? "https://open.bigmodel.cn/api/paas/v4" + : "https://api.openai.com/v1" + } />
@@ -3899,6 +3912,13 @@ export function AdminSettingsClient() {
+
+ setModelForm({ ...modelForm, isAgentModel: checked })} + /> + +
{/* 🆕 图片生成模型特性配置 */} {modelForm.supportsImageGen && ( @@ -4029,6 +4049,7 @@ export function AdminSettingsClient() {
{model.displayName} {model.isDefault && 默认} + {model.isAgentModel && Agent}
ID: {model.modelId} | 提供商: {provider?.name ?? "未知"} @@ -4046,6 +4067,7 @@ export function AdminSettingsClient() { supportsImageGen: model.supportsImageGen, isEnabled: model.isEnabled, isDefault: model.isDefault, + isAgentModel: model.isAgentModel || false, sortOrder: model.sortOrder || 0, fallbackModelId1: (model as any).fallbackModelId1 || "", fallbackModelId2: (model as any).fallbackModelId2 || "", diff --git a/app/(chat)/api/chat/route.ts b/app/(chat)/api/chat/route.ts index 9e8c990fb..8f47fe4dd 100644 --- a/app/(chat)/api/chat/route.ts +++ b/app/(chat)/api/chat/route.ts @@ -54,6 +54,7 @@ import { uploadFile } from "@/lib/storage/service"; import type { ChatMessage } from "@/lib/types"; import type { AppUsage } from "@/lib/usage"; import { convertToUIMessages, generateUUID } from "@/lib/utils"; +import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags"; import { generateTitleFromUserMessage } from "../../actions"; import { type PostRequestBody, postRequestBodySchema } from "./schema"; @@ -832,7 +833,13 @@ export async function POST(request: Request) { if (selectedModelIsUUID) { const selectedModelConfig = await getAiModelById({ id: requestedModelId }); - if (selectedModelConfig?.supportsImageGen && !selectedModelConfig?.isSubModel) { + if (selectedModelConfig && isAiModelAgentAuto(selectedModelConfig)) { + shouldRunAgentConversation = true; + shouldKeepSelectedModel = false; + console.log( + `[Smart Auto Mode] 🧠 Agent-only model detected, force auto routing: ${selectedModelConfig.displayName}` + ); + } else if (selectedModelConfig?.supportsImageGen && !selectedModelConfig?.isSubModel) { shouldRunAgentConversation = true; shouldKeepSelectedModel = true; console.log( diff --git a/app/(chat)/api/models/route.ts b/app/(chat)/api/models/route.ts index 5b13e17d1..80d11f1e6 100644 --- a/app/(chat)/api/models/route.ts +++ b/app/(chat)/api/models/route.ts @@ -1,6 +1,7 @@ import { auth } from "@/app/(auth)/auth"; import { getEnabledAiModelsWithProvider, getDefaultAiModel } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; +import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags"; // 获取前端可用的 AI 模型列表(需要登录但不需要管理员权限) export async function GET() { @@ -14,9 +15,12 @@ export async function GET() { // 只获取主模型(不包括子模型),子模型由后端根据 videoParams.mode 自动选择 const modelsWithProvider = await getEnabledAiModelsWithProvider(false); const defaultModel = await getDefaultAiModel(); + const visibleModelsWithProvider = modelsWithProvider.filter( + ({ model }) => !isAiModelAgentAuto(model) + ); // 转换为前端需要的格式 - const models = modelsWithProvider.map(({ model, provider }) => ({ + const models = visibleModelsWithProvider.map(({ model, provider }) => ({ id: model.id, modelId: model.modelId, displayName: model.displayName, @@ -31,9 +35,18 @@ export async function GET() { capabilities: model.capabilities, // 🆕 返回子模型的能力配置 })); + const resolvedDefaultModelId = + (defaultModel && + !isAiModelAgentAuto(defaultModel) && + models.some((model) => model.id === defaultModel.id) + ? defaultModel.id + : models.find((model) => model.isDefault)?.id) ?? + models[0]?.id ?? + null; + return Response.json({ models, - defaultModelId: defaultModel?.id ?? models[0]?.id ?? null, + defaultModelId: resolvedDefaultModelId, }); } catch (error) { if (error instanceof ChatSDKError) { @@ -42,4 +55,3 @@ export async function GET() { return new ChatSDKError("offline:chat").toResponse(); } } - diff --git a/app/(chat)/page.tsx b/app/(chat)/page.tsx index 50b3d68dd..d71d0f896 100644 --- a/app/(chat)/page.tsx +++ b/app/(chat)/page.tsx @@ -6,6 +6,7 @@ import { DataStreamHandler } from "@/components/data-stream-handler"; import { generateUUID } from "@/lib/utils"; import { auth } from "../(auth)/auth"; import { getDefaultAiModel, getEnabledAiModelsWithProvider } from "@/lib/db/queries"; +import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags"; export default function Page() { return ( @@ -33,14 +34,17 @@ async function NewChatPage() { if (!initialModelId) { // 尝试获取默认模型 const defaultModel = await getDefaultAiModel(); - - if (defaultModel) { + + if (defaultModel && !isAiModelAgentAuto(defaultModel)) { initialModelId = defaultModel.id; } else { // 如果没有设置默认模型,获取第一个启用的模型 const enabledModels = await getEnabledAiModelsWithProvider(); - if (enabledModels.length > 0) { - initialModelId = enabledModels[0].model.id; + const visibleModels = enabledModels.filter( + ({ model }) => !isAiModelAgentAuto(model) + ); + if (visibleModels.length > 0) { + initialModelId = visibleModels[0].model.id; } } } diff --git a/app/api/admin/ai-models/route.ts b/app/api/admin/ai-models/route.ts index 1e06e9009..29b6f6ac1 100644 --- a/app/api/admin/ai-models/route.ts +++ b/app/api/admin/ai-models/route.ts @@ -10,6 +10,7 @@ import { getAiProviderById, } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; +import { isAiModelAgentAuto } from "@/lib/db/ai-model-flags"; // 🆕 图片生成模型特性配置 Schema const imageGenConfigSchema = z.object({ @@ -35,6 +36,7 @@ const createModelSchema = z.object({ sortOrder: z.number().optional(), fallbackModelId1: z.string().max(128).optional(), fallbackModelId2: z.string().max(128).optional(), + isAgentModel: z.boolean().optional(), imageGenConfig: imageGenConfigSchema, // 🆕 图片生成特性配置 }); @@ -51,9 +53,17 @@ const updateModelSchema = z.object({ sortOrder: z.number().optional(), fallbackModelId1: z.string().max(128).optional(), fallbackModelId2: z.string().max(128).optional(), + isAgentModel: z.boolean().optional(), imageGenConfig: imageGenConfigSchema, // 🆕 图片生成特性配置 }); +function normalizeModel(model: any) { + return { + ...model, + isAgentModel: isAiModelAgentAuto(model), + }; +} + export async function GET(request: Request) { try { await requireAdmin(); @@ -67,16 +77,16 @@ export async function GET(request: Request) { if (!model) { return new ChatSDKError("bad_request:api", "Model not found").toResponse(); } - return Response.json({ model }); + return Response.json({ model: normalizeModel(model) }); } if (providerId) { const models = await getAiModelsByProviderId({ providerId }); - return Response.json({ models }); + return Response.json({ models: models.map(normalizeModel) }); } const models = await getAllAiModels(); - return Response.json({ models }); + return Response.json({ models: models.map(normalizeModel) }); } catch (error) { if (error instanceof ChatSDKError) { return error.toResponse(); @@ -100,7 +110,7 @@ export async function POST(request: Request) { const model = await createAiModel(data); - return Response.json({ model: model[0] }); + return Response.json({ model: normalizeModel(model[0]) }); } catch (error) { if (error instanceof ChatSDKError) { return error.toResponse(); @@ -129,7 +139,7 @@ export async function PUT(request: Request) { } console.log("[Admin API] Model updated successfully:", model[0].id); // Debug log - return Response.json({ model: model[0] }); + return Response.json({ model: normalizeModel(model[0]) }); } catch (error) { console.error("[Admin API] Update model error:", error); // Debug log if (error instanceof ChatSDKError) { @@ -164,4 +174,3 @@ export async function DELETE(request: Request) { return new ChatSDKError("offline:admin").toResponse(); } } - diff --git a/app/api/admin/ai-providers/route.ts b/app/api/admin/ai-providers/route.ts index 0e53d69eb..929077590 100644 --- a/app/api/admin/ai-providers/route.ts +++ b/app/api/admin/ai-providers/route.ts @@ -9,9 +9,26 @@ import { } from "@/lib/db/queries"; import { ChatSDKError } from "@/lib/errors"; +const providerTypes = [ + "alibaba", + "openai", + "custom", + "local", + "gemini", + "huanke-gemini", + "qianwen-video", + "volcano-vision", + "volcano-video", + "alibaba-image", + "ruxa-image", + "zhipu", +] as const; +type ProviderType = (typeof providerTypes)[number]; +type StoredProviderType = Exclude; + const createProviderSchema = z.object({ name: z.string().min(1).max(128), - type: z.enum(["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"]), + type: z.enum(providerTypes), baseUrl: z.string().min(1), // 改为简单的字符串验证,支持 localhost apiKey: z.string().min(1), isEnabled: z.boolean().optional(), @@ -20,12 +37,32 @@ const createProviderSchema = z.object({ const updateProviderSchema = z.object({ id: z.string().uuid(), name: z.string().min(1).max(128).optional(), - type: z.enum(["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"]).optional(), + type: z.enum(providerTypes).optional(), baseUrl: z.string().min(1).optional(), // 改为简单的字符串验证,支持 localhost apiKey: z.string().min(1).optional(), isEnabled: z.boolean().optional(), }); +const ZHIPU_BASE_URL = "https://open.bigmodel.cn/api/paas/v4"; + +function normalizeProviderTypeForStorage( + type?: ProviderType +): StoredProviderType | undefined { + if (!type) return undefined; + return type === "zhipu" ? "custom" : type; +} + +function isZhipuBaseUrl(baseUrl: string) { + return baseUrl.includes("open.bigmodel.cn"); +} + +function normalizeProviderTypeForResponse(provider: Record) { + if (provider.type === "custom" && typeof provider.baseUrl === "string" && isZhipuBaseUrl(provider.baseUrl)) { + return { ...provider, type: "zhipu" }; + } + return provider; +} + function maskApiKey(apiKey?: string) { if (!apiKey) return undefined; if (apiKey.length <= 8) return "****"; @@ -55,20 +92,22 @@ export async function GET(request: Request) { } // 隐藏 API Key 的部分内容 return Response.json({ - provider: { + provider: normalizeProviderTypeForResponse({ ...provider, apiKey: provider.apiKey.slice(0, 8) + "****" + provider.apiKey.slice(-4), - }, + }), }); } const providers = await getAllAiProviders(); // 隐藏 API Key 的部分内容 return Response.json({ - providers: providers.map((p) => ({ - ...p, - apiKey: p.apiKey.slice(0, 8) + "****" + p.apiKey.slice(-4), - })), + providers: providers.map((p) => + normalizeProviderTypeForResponse({ + ...p, + apiKey: p.apiKey.slice(0, 8) + "****" + p.apiKey.slice(-4), + }) + ), }); } catch (error) { if (error instanceof ChatSDKError) { @@ -87,14 +126,17 @@ export async function POST(request: Request) { const data = createProviderSchema.parse(body); console.log("[Admin API] Validation passed, creating provider..."); + const storageType = normalizeProviderTypeForStorage(data.type) ?? "custom"; const provider = await createAiProvider({ ...data, + type: storageType, + baseUrl: data.type === "zhipu" ? data.baseUrl || ZHIPU_BASE_URL : data.baseUrl, updatedBy: session.user.id, }); console.log("[Admin API] Provider created successfully:", provider[0].id); - return Response.json({ provider: provider[0] }); + return Response.json({ provider: normalizeProviderTypeForResponse(provider[0] as any) }); } catch (error) { console.error("[Admin API] Create provider error:", error); if (error instanceof ChatSDKError) { @@ -117,6 +159,7 @@ export async function PUT(request: Request) { const provider = await updateAiProvider({ ...data, + type: normalizeProviderTypeForStorage(data.type), updatedBy: session.user.id, }); @@ -124,7 +167,7 @@ export async function PUT(request: Request) { return new ChatSDKError("bad_request:api", "Provider not found").toResponse(); } - return Response.json({ provider: provider[0] }); + return Response.json({ provider: normalizeProviderTypeForResponse(provider[0] as any) }); } catch (error) { if (error instanceof ChatSDKError) { return error.toResponse(); diff --git a/components/multimodal-input.tsx b/components/multimodal-input.tsx index 2b30fa7ef..b48765aa3 100644 --- a/components/multimodal-input.tsx +++ b/components/multimodal-input.tsx @@ -1058,6 +1058,26 @@ function PureModelSelectorCompact({ setOptimisticModelId(selectedModelId); }, [selectedModelId]); + useEffect(() => { + if (displayModels.length === 0) return; + + const currentExists = displayModels.some((m) => m.id === selectedModelId); + if (currentExists) return; + + const fallbackModelId = + dynamicModels.find((m) => m.isDefault)?.id ?? + dynamicModels[0]?.id ?? + (showAutoModel ? "auto" : ""); + + if (!fallbackModelId) return; + + setOptimisticModelId(fallbackModelId); + onModelChange?.(fallbackModelId); + startTransition(() => { + saveChatModelAsCookie(fallbackModelId); + }); + }, [displayModels, dynamicModels, onModelChange, selectedModelId, showAutoModel]); + // 自动选择模型的逻辑 - 必须在所有条件 return 之前定义 const selectBestModel = useCallback(() => { const hasAttachments = attachments && attachments.length > 0; diff --git a/lib/ai/auto-agent-graph.ts b/lib/ai/auto-agent-graph.ts index 002184e07..e4a434ca1 100644 --- a/lib/ai/auto-agent-graph.ts +++ b/lib/ai/auto-agent-graph.ts @@ -65,16 +65,25 @@ async function generateAgentConversation( state: AutoAgentStateType ): Promise { try { - const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries"); + const { + getEnabledAiModelsWithProvider, + getAgentAiModelWithProvider, + } = await import("@/lib/db/queries"); const { getLanguageModelById } = await import("./dynamic-provider"); const allModels = await getEnabledAiModelsWithProvider(); + const agentModel = await getAgentAiModelWithProvider(); + const canUseAgentModel = Boolean( + agentModel && (!state.hasImage || agentModel.model.supportsVision) + ); const preferredVisionModel = allModels.find( (m) => m.provider.type === "alibaba" && m.model.supportsVision && m.model.isEnabled ); - const candidateModel = state.hasImage - ? preferredVisionModel || allModels.find((m) => m.model.supportsVision && m.model.isEnabled) - : allModels.find((m) => m.model.isEnabled && !m.model.isSubModel); + const candidateModel = + (canUseAgentModel ? agentModel : undefined) || + (state.hasImage + ? preferredVisionModel || allModels.find((m) => m.model.supportsVision && m.model.isEnabled) + : allModels.find((m) => m.model.isEnabled && !m.model.isSubModel)); if (!candidateModel) { return analysis.conversationalReply || null; diff --git a/lib/ai/dynamic-provider.ts b/lib/ai/dynamic-provider.ts index 5ee6982c8..ff5faa554 100644 --- a/lib/ai/dynamic-provider.ts +++ b/lib/ai/dynamic-provider.ts @@ -66,6 +66,17 @@ function getOrCreateProvider(provider: Pick !isAiModelAgentAuto(item.model) + ); + const allModels = nonAgentModels.length > 0 ? nonAgentModels : allModelsRaw; console.log("[Model Router] Available models:", allModels.length); // 获取意图对应的配置 diff --git a/lib/ai/prompt-optimizer.ts b/lib/ai/prompt-optimizer.ts index 890ee6aa5..87567121a 100644 --- a/lib/ai/prompt-optimizer.ts +++ b/lib/ai/prompt-optimizer.ts @@ -294,21 +294,26 @@ export async function optimizePromptWithLLM(params: { try { // 动态导入避免循环依赖 - const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries"); + const { + getEnabledAiModelsWithProvider, + getAgentAiModelWithProvider, + } = await import("@/lib/db/queries"); const { getLanguageModelById } = await import("./dynamic-provider"); // 获取千问3 VL 模型 const allModels = await getEnabledAiModelsWithProvider(); + const agentModel = await getAgentAiModelWithProvider(); const qwenModel = allModels.find( (m) => m.provider.type === "alibaba" && m.model.supportsVision ); + const selectedOptimizerModel = agentModel || qwenModel; - if (!qwenModel) { + if (!selectedOptimizerModel) { console.log("[Prompt Optimizer LLM] No Qwen3 VL model, falling back to template"); return optimizePrompt({ originalPrompt, intent, subIntent }); } - const model = await getLanguageModelById(qwenModel.model.id); + const model = await getLanguageModelById(selectedOptimizerModel.model.id); if (!model) { console.log("[Prompt Optimizer LLM] Failed to get model, falling back to template"); return optimizePrompt({ originalPrompt, intent, subIntent }); diff --git a/lib/ai/smart-analyzer.ts b/lib/ai/smart-analyzer.ts index 091b4c20c..71aa78b70 100644 --- a/lib/ai/smart-analyzer.ts +++ b/lib/ai/smart-analyzer.ts @@ -206,21 +206,29 @@ async function analyzWithLLM( context?: string ): Promise { // 使用动态导入避免循环依赖问题 - const { getEnabledAiModelsWithProvider } = await import("@/lib/db/queries"); + const { + getEnabledAiModelsWithProvider, + getAgentAiModelWithProvider, + } = await import("@/lib/db/queries"); const { getLanguageModelById } = await import("./dynamic-provider"); // 获取千问3 VL 模型 const allModels = await getEnabledAiModelsWithProvider(); + const agentModel = await getAgentAiModelWithProvider(); const qwenModel = allModels.find( (m) => m.provider.type === "alibaba" && m.model.supportsVision ); + const selectedAnalyzerModel = + (agentModel && (!hasImage || agentModel.model.supportsVision) + ? agentModel + : undefined) || qwenModel; - if (!qwenModel) { + if (!selectedAnalyzerModel) { console.log("[Smart Analyzer] No Qwen3 VL model found"); return null; } - const model = await getLanguageModelById(qwenModel.model.id); + const model = await getLanguageModelById(selectedAnalyzerModel.model.id); if (!model) { console.log("[Smart Analyzer] Failed to get language model"); return null; diff --git a/lib/db/ai-model-flags.ts b/lib/db/ai-model-flags.ts new file mode 100644 index 000000000..6ddde95b5 --- /dev/null +++ b/lib/db/ai-model-flags.ts @@ -0,0 +1,40 @@ +const AGENT_AUTO_MODEL_KEY = "agentAutoModel"; + +function toRecord(value: unknown): Record | null { + if (!value || typeof value !== "object" || Array.isArray(value)) { + return null; + } + return value as Record; +} + +export function isAiModelAgentAuto(input: { + defaultParams?: unknown; + capabilities?: unknown; +}): boolean { + const defaultParams = toRecord(input.defaultParams); + if (typeof defaultParams?.[AGENT_AUTO_MODEL_KEY] === "boolean") { + return defaultParams[AGENT_AUTO_MODEL_KEY] as boolean; + } + + const capabilities = toRecord(input.capabilities); + if (typeof capabilities?.[AGENT_AUTO_MODEL_KEY] === "boolean") { + return capabilities[AGENT_AUTO_MODEL_KEY] as boolean; + } + + return false; +} + +export function withAgentAutoModelFlag( + defaultParams: unknown, + enabled: boolean +): Record | null { + const next = { ...(toRecord(defaultParams) || {}) }; + + if (enabled) { + next[AGENT_AUTO_MODEL_KEY] = true; + } else { + delete next[AGENT_AUTO_MODEL_KEY]; + } + + return Object.keys(next).length > 0 ? next : null; +} diff --git a/lib/db/migrate.ts b/lib/db/migrate.ts index 576a2e01a..462042b46 100644 --- a/lib/db/migrate.ts +++ b/lib/db/migrate.ts @@ -28,7 +28,7 @@ const runMigrate = async () => { ADD CONSTRAINT "AiProvider_type_check" CHECK (type IN ( 'alibaba', 'openai', 'custom', 'local', 'gemini', - 'huanke-gemini', 'qianwen-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image' + 'huanke-gemini', 'qianwen-video', 'volcano-vision', 'volcano-video', 'alibaba-image', 'ruxa-image', 'zhipu' )); `); const end = Date.now(); diff --git a/lib/db/queries.ts b/lib/db/queries.ts index e0524f9ee..ebf7153c1 100644 --- a/lib/db/queries.ts +++ b/lib/db/queries.ts @@ -51,6 +51,10 @@ import { generatedImage, } from "./schema"; import { generateHashedPassword } from "./utils"; +import { + isAiModelAgentAuto, + withAgentAutoModelFlag, +} from "./ai-model-flags"; // Optionally, if not using email/pass login, you can // use the Drizzle adapter for Auth.js / NextAuth @@ -941,7 +945,7 @@ export async function createAiProvider({ updatedBy, }: { name: string; - type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image"; + type: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu"; baseUrl: string; apiKey: string; isEnabled?: boolean; @@ -979,7 +983,7 @@ export async function updateAiProvider({ }: { id: string; name?: string; - type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image"; + type?: "alibaba" | "openai" | "custom" | "local" | "gemini" | "huanke-gemini" | "qianwen-video" | "volcano-vision" | "volcano-video" | "alibaba-image" | "ruxa-image" | "zhipu"; baseUrl?: string; apiKey?: string; isEnabled?: boolean; @@ -1126,6 +1130,7 @@ export async function createAiModel({ fallbackModelId1, fallbackModelId2, imageGenConfig, + isAgentModel, }: { providerId: string; modelId: string; @@ -1139,6 +1144,7 @@ export async function createAiModel({ fallbackModelId1?: string; fallbackModelId2?: string; imageGenConfig?: ImageGenConfig; + isAgentModel?: boolean; }): Promise { try { // 如果设置为默认,先取消其他默认 @@ -1164,6 +1170,10 @@ export async function createAiModel({ fallbackModelId1: fallbackModelId1 || null, fallbackModelId2: fallbackModelId2 || null, imageGenConfig: imageGenConfig || null, + defaultParams: + isAgentModel === undefined + ? null + : withAgentAutoModelFlag(null, isAgentModel), }) .returning(); } catch (_error) { @@ -1188,6 +1198,7 @@ export async function updateAiModel({ fallbackModelId1, fallbackModelId2, imageGenConfig, + isAgentModel, }: { id: string; providerId?: string; @@ -1202,6 +1213,7 @@ export async function updateAiModel({ fallbackModelId1?: string; fallbackModelId2?: string; imageGenConfig?: ImageGenConfig; + isAgentModel?: boolean; }): Promise { try { // 如果设置为默认,先取消其他默认 @@ -1228,6 +1240,22 @@ export async function updateAiModel({ if (fallbackModelId2 !== undefined) updateData.fallbackModelId2 = fallbackModelId2 || null; // 🆕 图片生成配置 if (imageGenConfig !== undefined) updateData.imageGenConfig = imageGenConfig || null; + if (isAgentModel !== undefined) { + const [existingModel] = await db + .select({ defaultParams: aiModel.defaultParams }) + .from(aiModel) + .where(eq(aiModel.id, id)) + .limit(1); + + if (!existingModel) { + return []; + } + + updateData.defaultParams = withAgentAutoModelFlag( + existingModel.defaultParams, + isAgentModel + ); + } return await db .update(aiModel) @@ -1476,6 +1504,20 @@ export async function getEnabledAiModelsWithProvider(includeSubModels: boolean = } } +export async function getAgentAiModelWithProvider() { + const models = await getEnabledAiModelsWithProvider(false); + const candidates = models + .filter(({ model }) => isAiModelAgentAuto(model)) + .sort((a, b) => { + if (a.model.isDefault !== b.model.isDefault) { + return Number(b.model.isDefault) - Number(a.model.isDefault); + } + return (a.model.sortOrder ?? 0) - (b.model.sortOrder ?? 0); + }); + + return candidates[0]; +} + /** * 🆕 获取指定父模型的所有子模型 * 用于火山引擎视频等需要智能选择模型的场景 diff --git a/lib/db/schema.ts b/lib/db/schema.ts index 3988ed684..0fe20219e 100644 --- a/lib/db/schema.ts +++ b/lib/db/schema.ts @@ -215,7 +215,7 @@ export type ToolConfig = InferSelectModel; export const aiProvider = pgTable("AiProvider", { id: uuid("id").primaryKey().notNull().defaultRandom(), name: varchar("name", { length: 128 }).notNull(), // 提供商名称,如 "阿里云通义千问" - type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image"] }) + type: varchar("type", { enum: ["alibaba", "openai", "custom", "local", "gemini", "huanke-gemini", "qianwen-video", "volcano-vision", "volcano-video", "alibaba-image", "ruxa-image", "zhipu"] }) .notNull() .default("custom"), // 提供商类型 baseUrl: text("baseUrl").notNull(), // API 基础 URL