fix: enable local moonshot detail agent option
This commit is contained in:
parent
b702440347
commit
b99eb6cf7a
@ -22,6 +22,13 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import {
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID,
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY,
|
||||
normalizeDetailAgentLocalMoonshotConfig,
|
||||
} from "@/lib/ai/detail-agent-model-config";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import {
|
||||
@ -511,9 +518,6 @@ const PROVIDER_DEFAULT_BASE_URL: Record<string, string> = {
|
||||
};
|
||||
|
||||
const LEMON_OPENAI_BASE_URL = "https://new.lemonapi.site/v1";
|
||||
const MOONSHOT_OFFICIAL_BASE_URL = "https://api.moonshot.cn/v1";
|
||||
const MOONSHOT_KIMI_K25_MODEL_ID = "kimi-k2.5";
|
||||
|
||||
const LEMON_PROVIDER_PRESET = {
|
||||
name: "LemonAPI Gemini",
|
||||
type: "openai" as const,
|
||||
@ -545,7 +549,10 @@ function isLemonApiBaseUrl(value?: string | null) {
|
||||
}
|
||||
|
||||
function isMoonshotOfficialBaseUrl(value?: string | null) {
|
||||
return String(value || "").trim().replace(/\/+$/, "") === MOONSHOT_OFFICIAL_BASE_URL;
|
||||
return (
|
||||
String(value || "").trim().replace(/\/+$/, "") ===
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL
|
||||
);
|
||||
}
|
||||
|
||||
export function AdminSettingsClient() {
|
||||
@ -605,6 +612,11 @@ export function AdminSettingsClient() {
|
||||
"/api/auth-settings",
|
||||
fetcher,
|
||||
);
|
||||
const { data: moonshotDetailAgentSettingData, mutate: mutateMoonshotDetailAgentSetting } =
|
||||
useSWR<{ setting?: { value?: unknown } }>(
|
||||
`/api/admin/settings?key=${DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY}`,
|
||||
fetcher,
|
||||
);
|
||||
|
||||
// 🆕 生图记录数据
|
||||
const imageQueryParams = new URLSearchParams({
|
||||
@ -870,48 +882,20 @@ export function AdminSettingsClient() {
|
||||
}, [siteSettingsData]);
|
||||
|
||||
useEffect(() => {
|
||||
const configuredModelId = siteSettings.canvasDetailAgentModelId;
|
||||
const providerList = providersData?.providers || [];
|
||||
const modelList = modelsData?.models || [];
|
||||
|
||||
if (!configuredModelId) {
|
||||
const moonshotProvider = providerList.find(
|
||||
(provider) => provider.type === "openai" && isMoonshotOfficialBaseUrl(provider.baseUrl),
|
||||
);
|
||||
setMoonshotDetailAgentConfig((prev) => ({
|
||||
...prev,
|
||||
providerId: moonshotProvider?.id || "",
|
||||
providerName: moonshotProvider?.name || "Moonshot AI (Official)",
|
||||
baseUrl: moonshotProvider?.baseUrl || MOONSHOT_OFFICIAL_BASE_URL,
|
||||
modelId: prev.modelId || MOONSHOT_KIMI_K25_MODEL_ID,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
const configuredModel = modelList.find((model) => model.id === configuredModelId) || null;
|
||||
const configuredProvider = configuredModel
|
||||
? providerList.find((provider) => provider.id === configuredModel.providerId)
|
||||
: null;
|
||||
|
||||
if (!configuredModel || !configuredProvider || !isMoonshotOfficialBaseUrl(configuredProvider.baseUrl)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setMoonshotDetailAgentConfig((prev) => ({
|
||||
...prev,
|
||||
providerId: configuredProvider.id,
|
||||
modelEntryId: configuredModel.id,
|
||||
providerName: configuredProvider.name || "Moonshot AI (Official)",
|
||||
baseUrl: configuredProvider.baseUrl || MOONSHOT_OFFICIAL_BASE_URL,
|
||||
modelId: configuredModel.modelId || "",
|
||||
displayName: configuredModel.displayName || "官方 Kimi K2.5",
|
||||
isEnabled: configuredModel.isEnabled !== false && configuredProvider.isEnabled !== false,
|
||||
}));
|
||||
}, [
|
||||
modelsData?.models,
|
||||
providersData?.providers,
|
||||
siteSettings.canvasDetailAgentModelId,
|
||||
]);
|
||||
const normalized = normalizeDetailAgentLocalMoonshotConfig(
|
||||
moonshotDetailAgentSettingData?.setting?.value,
|
||||
);
|
||||
setMoonshotDetailAgentConfig({
|
||||
providerId: "",
|
||||
modelEntryId: "",
|
||||
providerName: normalized.providerName,
|
||||
baseUrl: normalized.baseUrl,
|
||||
apiKey: "",
|
||||
modelId: normalized.modelId,
|
||||
displayName: normalized.displayName,
|
||||
isEnabled: normalized.enabled,
|
||||
});
|
||||
}, [moonshotDetailAgentSettingData]);
|
||||
|
||||
const featuredCategoryOptions = useMemo(() => {
|
||||
const values = (siteSettings.surpriseCategories || [])
|
||||
@ -1022,9 +1006,9 @@ export function AdminSettingsClient() {
|
||||
providerId: "",
|
||||
modelEntryId: "",
|
||||
providerName: "Moonshot AI (Official)",
|
||||
baseUrl: MOONSHOT_OFFICIAL_BASE_URL,
|
||||
baseUrl: DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
|
||||
apiKey: "",
|
||||
modelId: MOONSHOT_KIMI_K25_MODEL_ID,
|
||||
modelId: DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID,
|
||||
displayName: "官方 Kimi K2.5",
|
||||
isEnabled: true,
|
||||
});
|
||||
@ -2697,9 +2681,11 @@ export function AdminSettingsClient() {
|
||||
const handleSaveMoonshotDetailAgentConfig = async () => {
|
||||
try {
|
||||
const trimmedBaseUrl =
|
||||
moonshotDetailAgentConfig.baseUrl.trim() || MOONSHOT_OFFICIAL_BASE_URL;
|
||||
moonshotDetailAgentConfig.baseUrl.trim() ||
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL;
|
||||
const trimmedModelId =
|
||||
moonshotDetailAgentConfig.modelId.trim() || MOONSHOT_KIMI_K25_MODEL_ID;
|
||||
moonshotDetailAgentConfig.modelId.trim() ||
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID;
|
||||
const trimmedDisplayName =
|
||||
moonshotDetailAgentConfig.displayName.trim() || "官方 Kimi K2.5";
|
||||
const trimmedProviderName =
|
||||
@ -2720,90 +2706,31 @@ export function AdminSettingsClient() {
|
||||
return;
|
||||
}
|
||||
|
||||
const providerPayload: Record<string, any> = moonshotDetailAgentConfig.providerId
|
||||
? {
|
||||
id: moonshotDetailAgentConfig.providerId,
|
||||
name: trimmedProviderName,
|
||||
type: "openai",
|
||||
baseUrl: trimmedBaseUrl,
|
||||
isEnabled: moonshotDetailAgentConfig.isEnabled,
|
||||
}
|
||||
: {
|
||||
name: trimmedProviderName,
|
||||
type: "openai",
|
||||
const settingResponse = await fetch("/api/admin/settings", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
key: DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY,
|
||||
value: {
|
||||
enabled: moonshotDetailAgentConfig.isEnabled,
|
||||
providerName: trimmedProviderName,
|
||||
baseUrl: trimmedBaseUrl,
|
||||
apiKey: moonshotDetailAgentConfig.apiKey.trim(),
|
||||
isEnabled: moonshotDetailAgentConfig.isEnabled,
|
||||
};
|
||||
|
||||
if (moonshotDetailAgentConfig.providerId && moonshotDetailAgentConfig.apiKey.trim()) {
|
||||
providerPayload.apiKey = moonshotDetailAgentConfig.apiKey.trim();
|
||||
}
|
||||
|
||||
const providerResponse = await fetch("/api/admin/ai-providers", {
|
||||
method: moonshotDetailAgentConfig.providerId ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(providerPayload),
|
||||
});
|
||||
const providerResult = await providerResponse.json().catch(() => ({}));
|
||||
if (!providerResponse.ok || !providerResult?.provider?.id) {
|
||||
throw new Error(String(providerResult?.error || "保存 Moonshot 提供商失败"));
|
||||
}
|
||||
|
||||
const providerId = String(providerResult.provider.id);
|
||||
const existingModel =
|
||||
(moonshotDetailAgentConfig.modelEntryId &&
|
||||
modelsData?.models?.find((model) => model.id === moonshotDetailAgentConfig.modelEntryId)) ||
|
||||
modelsData?.models?.find(
|
||||
(model) => model.providerId === providerId && model.modelId === trimmedModelId,
|
||||
) ||
|
||||
null;
|
||||
|
||||
const modelPayload: Record<string, any> = existingModel
|
||||
? {
|
||||
id: existingModel.id,
|
||||
providerId,
|
||||
modelId: trimmedModelId,
|
||||
displayName: trimmedDisplayName,
|
||||
description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审",
|
||||
supportsVision: true,
|
||||
supportsImageGen: false,
|
||||
isEnabled: moonshotDetailAgentConfig.isEnabled,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: true,
|
||||
}
|
||||
: {
|
||||
providerId,
|
||||
modelId: trimmedModelId,
|
||||
displayName: trimmedDisplayName,
|
||||
description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审",
|
||||
supportsVision: true,
|
||||
supportsImageGen: false,
|
||||
isEnabled: moonshotDetailAgentConfig.isEnabled,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: true,
|
||||
};
|
||||
|
||||
const modelResponse = await fetch("/api/admin/ai-models", {
|
||||
method: existingModel ? "PUT" : "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(modelPayload),
|
||||
},
|
||||
}),
|
||||
});
|
||||
const modelResult = await modelResponse.json().catch(() => ({}));
|
||||
const savedModel = modelResult?.model;
|
||||
if (!modelResponse.ok || !savedModel?.id) {
|
||||
throw new Error(String(modelResult?.error || "保存 Kimi K2.5 模型失败"));
|
||||
const settingResult = await settingResponse.json().catch(() => ({}));
|
||||
if (!settingResponse.ok) {
|
||||
throw new Error(String(settingResult?.message || "保存 Moonshot 本地AI配置失败"));
|
||||
}
|
||||
|
||||
const settingsResponse = await fetch("/api/site-settings", {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
canvasDetailAgentModelId: savedModel.id,
|
||||
canvasDetailAgentModelId: DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
}),
|
||||
});
|
||||
if (!settingsResponse.ok) {
|
||||
@ -2812,12 +2739,10 @@ export function AdminSettingsClient() {
|
||||
|
||||
setSiteSettings((prev) => ({
|
||||
...prev,
|
||||
canvasDetailAgentModelId: savedModel.id,
|
||||
canvasDetailAgentModelId: DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
}));
|
||||
setMoonshotDetailAgentConfig((prev) => ({
|
||||
...prev,
|
||||
providerId,
|
||||
modelEntryId: String(savedModel.id),
|
||||
providerName: trimmedProviderName,
|
||||
baseUrl: trimmedBaseUrl,
|
||||
modelId: trimmedModelId,
|
||||
@ -2825,8 +2750,7 @@ export function AdminSettingsClient() {
|
||||
apiKey: "",
|
||||
}));
|
||||
|
||||
mutateProviders();
|
||||
mutateModels();
|
||||
mutateMoonshotDetailAgentSetting();
|
||||
mutateSiteSettings();
|
||||
toast.success("Moonshot 官方 Kimi K2.5 已绑定为 AI详情 Agent 专用模型");
|
||||
} catch (error) {
|
||||
@ -5756,6 +5680,11 @@ export function AdminSettingsClient() {
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="agent-default">沿用Agent对话默认模型</SelectItem>
|
||||
{moonshotDetailAgentConfig.isEnabled ? (
|
||||
<SelectItem value={DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID}>
|
||||
{moonshotDetailAgentConfig.displayName} · 本地AI配置
|
||||
</SelectItem>
|
||||
) : null}
|
||||
{detailAgentModelOptions.map((model) => (
|
||||
<SelectItem key={model.id} value={model.id}>
|
||||
{model.displayName}
|
||||
@ -8310,10 +8239,10 @@ export function AdminSettingsClient() {
|
||||
baseUrl: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={MOONSHOT_OFFICIAL_BASE_URL}
|
||||
placeholder={DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
根据 Moonshot 官方文档,OpenAI 兼容接口地址为 `{MOONSHOT_OFFICIAL_BASE_URL}`
|
||||
根据 Moonshot 官方文档,OpenAI 兼容接口地址为 `{DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL}`
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -8350,7 +8279,7 @@ export function AdminSettingsClient() {
|
||||
modelId: e.target.value,
|
||||
}))
|
||||
}
|
||||
placeholder={MOONSHOT_KIMI_K25_MODEL_ID}
|
||||
placeholder={DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID}
|
||||
/>
|
||||
<div className="text-xs text-muted-foreground">
|
||||
已按你提供的 Moonshot 官方文档默认填写 `kimi-k2.5`;保存后系统会自动创建/更新对应模型记录。
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { z } from "zod";
|
||||
import { requireAdmin } from "@/lib/middleware/admin";
|
||||
import {
|
||||
createAiModel,
|
||||
createAiProvider,
|
||||
getAiModelById,
|
||||
getAiProviderById,
|
||||
getAllAiModels,
|
||||
getAllSystemSettings,
|
||||
getSystemSetting,
|
||||
upsertSystemSetting,
|
||||
updateAiModel,
|
||||
updateAiProvider,
|
||||
} from "@/lib/db/queries";
|
||||
import { ChatSDKError } from "@/lib/errors";
|
||||
import { probeBflModelSupport } from "@/lib/realtime/bfl-capability";
|
||||
@ -12,12 +19,33 @@ import {
|
||||
normalizeRealtimeCanvasConfigForStorage,
|
||||
resolveRealtimeProviders,
|
||||
} from "@/lib/realtime/config";
|
||||
import { db } from "@/lib/db";
|
||||
import { siteSettings } from "@/lib/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const settingSchema = z.object({
|
||||
key: z.string(),
|
||||
value: z.any(),
|
||||
});
|
||||
|
||||
const moonshotDetailAgentConfigSchema = z.object({
|
||||
providerId: z.string().uuid().optional(),
|
||||
modelEntryId: z.string().uuid().optional(),
|
||||
providerName: z.string().trim().min(1).max(128).optional(),
|
||||
baseUrl: z.string().trim().min(1),
|
||||
apiKey: z.string().optional(),
|
||||
modelId: z.string().trim().min(1).max(128).optional(),
|
||||
displayName: z.string().trim().min(1).max(128).optional(),
|
||||
isEnabled: z.boolean().optional(),
|
||||
});
|
||||
|
||||
const MOONSHOT_OFFICIAL_BASE_URL = "https://api.moonshot.cn/v1";
|
||||
const MOONSHOT_KIMI_K25_MODEL_ID = "kimi-k2.5";
|
||||
|
||||
function isMoonshotOfficialBaseUrl(value: string) {
|
||||
return String(value || "").trim().replace(/\/+$/, "") === MOONSHOT_OFFICIAL_BASE_URL;
|
||||
}
|
||||
|
||||
function isObjectLike(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
@ -58,6 +86,132 @@ export async function POST(request: Request) {
|
||||
|
||||
let nextValue = value;
|
||||
|
||||
if (key === "moonshot_detail_agent_config") {
|
||||
const config = moonshotDetailAgentConfigSchema.parse(value || {});
|
||||
const normalizedBaseUrl = String(config.baseUrl || "").trim() || MOONSHOT_OFFICIAL_BASE_URL;
|
||||
const normalizedModelId = String(config.modelId || "").trim() || MOONSHOT_KIMI_K25_MODEL_ID;
|
||||
const normalizedDisplayName = String(config.displayName || "").trim() || "官方 Kimi K2.5";
|
||||
const normalizedProviderName =
|
||||
String(config.providerName || "").trim() || "Moonshot AI (Official)";
|
||||
const normalizedApiKey = String(config.apiKey || "").trim();
|
||||
const enabled = config.isEnabled !== false;
|
||||
|
||||
if (!isMoonshotOfficialBaseUrl(normalizedBaseUrl)) {
|
||||
return new ChatSDKError(
|
||||
"bad_request:api",
|
||||
`Moonshot 官方地址必须为 ${MOONSHOT_OFFICIAL_BASE_URL}`,
|
||||
).toResponse();
|
||||
}
|
||||
|
||||
if (!config.providerId && !normalizedApiKey) {
|
||||
return new ChatSDKError("bad_request:api", "缺少 Moonshot 官方 API Key").toResponse();
|
||||
}
|
||||
|
||||
const [existingSiteSettings] = await db.select().from(siteSettings).limit(1);
|
||||
|
||||
const providerRows = config.providerId
|
||||
? await updateAiProvider({
|
||||
id: config.providerId,
|
||||
name: normalizedProviderName,
|
||||
type: "openai",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
apiKey: normalizedApiKey || undefined,
|
||||
isEnabled: enabled,
|
||||
updatedBy: session.user.id,
|
||||
})
|
||||
: await createAiProvider({
|
||||
name: normalizedProviderName,
|
||||
type: "openai",
|
||||
baseUrl: normalizedBaseUrl,
|
||||
apiKey: normalizedApiKey,
|
||||
isEnabled: enabled,
|
||||
updatedBy: session.user.id,
|
||||
});
|
||||
|
||||
const provider = providerRows[0];
|
||||
if (!provider) {
|
||||
return new ChatSDKError("bad_request:database", "保存 Moonshot 提供商失败").toResponse();
|
||||
}
|
||||
|
||||
let existingModel =
|
||||
config.modelEntryId ? await getAiModelById({ id: config.modelEntryId }) : undefined;
|
||||
|
||||
if (!existingModel) {
|
||||
const allModels = await getAllAiModels();
|
||||
existingModel =
|
||||
allModels.find(
|
||||
(model) => model.providerId === provider.id && model.modelId === normalizedModelId,
|
||||
) || undefined;
|
||||
}
|
||||
|
||||
const modelRows = existingModel
|
||||
? await updateAiModel({
|
||||
id: existingModel.id,
|
||||
providerId: provider.id,
|
||||
modelId: normalizedModelId,
|
||||
displayName: normalizedDisplayName,
|
||||
description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审",
|
||||
supportsVision: true,
|
||||
supportsImageGen: false,
|
||||
isEnabled: enabled,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: true,
|
||||
})
|
||||
: await createAiModel({
|
||||
providerId: provider.id,
|
||||
modelId: normalizedModelId,
|
||||
displayName: normalizedDisplayName,
|
||||
description: "Moonshot 官方 Kimi K2.5,专用于 AI详情生成 的规划与排版复审",
|
||||
supportsVision: true,
|
||||
supportsImageGen: false,
|
||||
isEnabled: enabled,
|
||||
isHidden: false,
|
||||
isDefault: false,
|
||||
isAgentModel: false,
|
||||
isMultimodal: true,
|
||||
});
|
||||
|
||||
const model = modelRows[0];
|
||||
if (!model) {
|
||||
return new ChatSDKError("bad_request:database", "保存 Kimi K2.5 模型失败").toResponse();
|
||||
}
|
||||
|
||||
if (existingSiteSettings) {
|
||||
await db
|
||||
.update(siteSettings)
|
||||
.set({
|
||||
canvasDetailAgentModelId: model.id,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: session.user.id,
|
||||
})
|
||||
.where(eq(siteSettings.id, existingSiteSettings.id));
|
||||
} else {
|
||||
await db.insert(siteSettings).values({
|
||||
siteName: "AI Chatbot",
|
||||
canvasDetailAgentModelId: model.id,
|
||||
updatedBy: session.user.id,
|
||||
});
|
||||
}
|
||||
|
||||
return Response.json({
|
||||
setting: {
|
||||
key: "moonshot_detail_agent_config",
|
||||
value: {
|
||||
providerId: provider.id,
|
||||
modelEntryId: model.id,
|
||||
providerName: provider.name,
|
||||
baseUrl: provider.baseUrl,
|
||||
modelId: model.modelId,
|
||||
displayName: model.displayName,
|
||||
isEnabled: model.isEnabled,
|
||||
canvasDetailAgentModelId: model.id,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (key === "realtime_canvas_config") {
|
||||
if (!isObjectLike(value)) {
|
||||
return new ChatSDKError("bad_request:api", "realtime_canvas_config value 必须为对象").toResponse();
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
isAiModelMultimodal,
|
||||
} from "@/lib/db/ai-model-flags";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
import { resolveDetailAgentLocalMoonshotModel } from "@/lib/ai/detail-agent-model-runtime";
|
||||
import { inlineVisionImages } from "@/lib/ai/vision-inline";
|
||||
import { filterReachableRemoteUrls } from "@/lib/net/url-access";
|
||||
import {
|
||||
@ -1806,6 +1807,9 @@ export async function POST(request: Request) {
|
||||
getAgentAiModelWithProvider(),
|
||||
getSiteSettings(),
|
||||
]);
|
||||
const localDetailAgentResolved = await resolveDetailAgentLocalMoonshotModel({
|
||||
siteSettingModelId: currentSiteSettings?.canvasDetailAgentModelId,
|
||||
});
|
||||
const reasoningCompatible = allModels.filter(
|
||||
(item) =>
|
||||
!item.model.isSubModel &&
|
||||
@ -1829,45 +1833,65 @@ export async function POST(request: Request) {
|
||||
const preferredAgentConfig = configuredDetailAgentConfig || fallbackAgentConfig;
|
||||
let plannerConfig = preferredAgentConfig;
|
||||
|
||||
if (referenceImages.length > 0) {
|
||||
if (!localDetailAgentResolved && referenceImages.length > 0) {
|
||||
if (!plannerConfig || !supportsVisionPlanning(plannerConfig.model)) {
|
||||
plannerConfig = reasoningCompatible.find((item) => supportsVisionPlanning(item.model));
|
||||
}
|
||||
}
|
||||
if (!plannerConfig) {
|
||||
if (!localDetailAgentResolved && !plannerConfig) {
|
||||
plannerConfig = reasoningCompatible[0];
|
||||
}
|
||||
|
||||
if (!plannerConfig) {
|
||||
if (!localDetailAgentResolved && !plannerConfig) {
|
||||
return NextResponse.json({ success: true, data: fallback });
|
||||
}
|
||||
|
||||
const selectedModelRef = localDetailAgentResolved
|
||||
? localDetailAgentResolved.modelRef
|
||||
: plannerConfig!.model;
|
||||
const selectedProviderRef = localDetailAgentResolved
|
||||
? localDetailAgentResolved.providerRef
|
||||
: {
|
||||
id: plannerConfig!.provider.id,
|
||||
name: plannerConfig!.provider.name,
|
||||
type: plannerConfig!.provider.type,
|
||||
baseUrl: plannerConfig!.provider.baseUrl,
|
||||
};
|
||||
|
||||
console.log("[Canvas Detail Plan] Planner model selected:", {
|
||||
id: plannerConfig.model.id,
|
||||
displayName: plannerConfig.model.displayName,
|
||||
modelId: plannerConfig.model.modelId,
|
||||
codingModelId: getAiModelAgentCodingModelId(plannerConfig.model),
|
||||
providerType: plannerConfig.provider.type,
|
||||
supportsVision: plannerConfig.model.supportsVision,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
modelId: selectedModelRef.modelId,
|
||||
codingModelId: localDetailAgentResolved
|
||||
? null
|
||||
: getAiModelAgentCodingModelId(plannerConfig!.model),
|
||||
providerType: selectedProviderRef.type,
|
||||
supportsVision: selectedModelRef.supportsVision,
|
||||
detailAgentSettingModelId: currentSiteSettings?.canvasDetailAgentModelId || null,
|
||||
selectionSource:
|
||||
configuredDetailAgentConfig?.model.id === plannerConfig.model.id
|
||||
localDetailAgentResolved
|
||||
? "detail-agent-local-config"
|
||||
: configuredDetailAgentConfig?.model.id === plannerConfig?.model.id
|
||||
? "detail-agent-setting"
|
||||
: fallbackAgentConfig?.model.id === plannerConfig.model.id
|
||||
: fallbackAgentConfig?.model.id === plannerConfig?.model.id
|
||||
? "agent-default"
|
||||
: "vision-fallback",
|
||||
multimodalFlag: isAiModelMultimodal({
|
||||
capabilities: plannerConfig.model.capabilities,
|
||||
defaultParams: plannerConfig.model.defaultParams,
|
||||
capabilities: selectedModelRef.capabilities,
|
||||
defaultParams: selectedModelRef.defaultParams,
|
||||
}),
|
||||
hasReferenceImages: referenceImages.length > 0,
|
||||
});
|
||||
|
||||
const codingModelId = getAiModelAgentCodingModelId(plannerConfig.model);
|
||||
const model = await getLanguageModelById(
|
||||
plannerConfig.model.id,
|
||||
codingModelId
|
||||
);
|
||||
const codingModelId = localDetailAgentResolved
|
||||
? null
|
||||
: getAiModelAgentCodingModelId(plannerConfig!.model);
|
||||
const model = localDetailAgentResolved
|
||||
? localDetailAgentResolved.model
|
||||
: await getLanguageModelById(
|
||||
plannerConfig!.model.id,
|
||||
codingModelId
|
||||
);
|
||||
if (!model) {
|
||||
return NextResponse.json({ success: true, data: fallback });
|
||||
}
|
||||
@ -1898,9 +1922,12 @@ export async function POST(request: Request) {
|
||||
.join("\n");
|
||||
|
||||
let imageInputs: Array<{ type: "image"; image: string }> = [];
|
||||
if (supportsVisionPlanning(plannerConfig.model) && referenceImages.length > 0) {
|
||||
if (
|
||||
(localDetailAgentResolved || supportsVisionPlanning(plannerConfig!.model)) &&
|
||||
referenceImages.length > 0
|
||||
) {
|
||||
try {
|
||||
if (isVolcanoResponsesPlanner(plannerConfig)) {
|
||||
if (!localDetailAgentResolved && isVolcanoResponsesPlanner(plannerConfig!)) {
|
||||
imageInputs = referenceImages
|
||||
.slice(0, 6)
|
||||
.map((url) => ({ type: "image" as const, image: url }));
|
||||
@ -2042,9 +2069,9 @@ export async function POST(request: Request) {
|
||||
issues: [fallbackReason],
|
||||
},
|
||||
model: {
|
||||
id: plannerConfig.model.id,
|
||||
displayName: plannerConfig.model.displayName,
|
||||
providerType: plannerConfig.provider.type,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
providerType: selectedProviderRef.type,
|
||||
},
|
||||
preflight: {
|
||||
passed: false,
|
||||
@ -2224,9 +2251,9 @@ export async function POST(request: Request) {
|
||||
})
|
||||
: undefined,
|
||||
model: {
|
||||
id: plannerConfig.model.id,
|
||||
displayName: plannerConfig.model.displayName,
|
||||
providerType: plannerConfig.provider.type,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
providerType: selectedProviderRef.type,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ import {
|
||||
isAiModelMultimodal,
|
||||
} from "@/lib/db/ai-model-flags";
|
||||
import { getLanguageModelById } from "@/lib/ai/dynamic-provider";
|
||||
import { resolveDetailAgentLocalMoonshotModel } from "@/lib/ai/detail-agent-model-runtime";
|
||||
import { inlineVisionImages } from "@/lib/ai/vision-inline";
|
||||
import {
|
||||
buildDetailUiProtocol,
|
||||
@ -195,6 +196,9 @@ export async function POST(request: Request) {
|
||||
getAgentAiModelWithProvider(),
|
||||
getSiteSettings(),
|
||||
]);
|
||||
const localDetailAgentResolved = await resolveDetailAgentLocalMoonshotModel({
|
||||
siteSettingModelId: currentSiteSettings?.canvasDetailAgentModelId,
|
||||
});
|
||||
const reviewCompatible = allModels.filter(
|
||||
(item) =>
|
||||
!item.model.isSubModel &&
|
||||
@ -217,7 +221,7 @@ export async function POST(request: Request) {
|
||||
? reviewCompatible.find((item) => item.model.id === agentModel.model.id)
|
||||
: undefined;
|
||||
let reviewerConfig = configuredDetailAgentConfig || fallbackAgentConfig;
|
||||
if (!reviewerConfig || !supportsVisionReview(reviewerConfig.model)) {
|
||||
if (!localDetailAgentResolved && (!reviewerConfig || !supportsVisionReview(reviewerConfig.model))) {
|
||||
reviewerConfig = reviewCompatible.find((item) => supportsVisionReview(item.model));
|
||||
}
|
||||
|
||||
@ -253,7 +257,7 @@ export async function POST(request: Request) {
|
||||
});
|
||||
const currentUiValidation = validateDetailUiProtocol(currentUiProtocol);
|
||||
|
||||
if (!reviewerConfig) {
|
||||
if (!localDetailAgentResolved && !reviewerConfig) {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
data: {
|
||||
@ -272,23 +276,40 @@ export async function POST(request: Request) {
|
||||
});
|
||||
}
|
||||
|
||||
const codingModelId = getAiModelAgentCodingModelId(reviewerConfig.model);
|
||||
const model = await getLanguageModelById(
|
||||
reviewerConfig.model.id,
|
||||
codingModelId
|
||||
);
|
||||
const selectedModelRef = localDetailAgentResolved
|
||||
? localDetailAgentResolved.modelRef
|
||||
: reviewerConfig!.model;
|
||||
const selectedProviderRef = localDetailAgentResolved
|
||||
? localDetailAgentResolved.providerRef
|
||||
: {
|
||||
id: reviewerConfig!.provider.id,
|
||||
name: reviewerConfig!.provider.name,
|
||||
type: reviewerConfig!.provider.type,
|
||||
baseUrl: reviewerConfig!.provider.baseUrl,
|
||||
};
|
||||
const codingModelId = localDetailAgentResolved
|
||||
? null
|
||||
: getAiModelAgentCodingModelId(reviewerConfig!.model);
|
||||
const model = localDetailAgentResolved
|
||||
? localDetailAgentResolved.model
|
||||
: await getLanguageModelById(
|
||||
reviewerConfig!.model.id,
|
||||
codingModelId
|
||||
);
|
||||
console.log("[Canvas Detail Review] Reviewer model selected:", {
|
||||
id: reviewerConfig.model.id,
|
||||
displayName: reviewerConfig.model.displayName,
|
||||
modelId: reviewerConfig.model.modelId,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
modelId: selectedModelRef.modelId,
|
||||
codingModelId,
|
||||
providerType: reviewerConfig.provider.type,
|
||||
supportsVision: reviewerConfig.model.supportsVision,
|
||||
providerType: selectedProviderRef.type,
|
||||
supportsVision: selectedModelRef.supportsVision,
|
||||
detailAgentSettingModelId: currentSiteSettings?.canvasDetailAgentModelId || null,
|
||||
selectionSource:
|
||||
configuredDetailAgentConfig?.model.id === reviewerConfig.model.id
|
||||
localDetailAgentResolved
|
||||
? "detail-agent-local-config"
|
||||
: configuredDetailAgentConfig?.model.id === reviewerConfig?.model.id
|
||||
? "detail-agent-setting"
|
||||
: fallbackAgentConfig?.model.id === reviewerConfig.model.id
|
||||
: fallbackAgentConfig?.model.id === reviewerConfig?.model.id
|
||||
? "agent-default"
|
||||
: "vision-fallback",
|
||||
});
|
||||
@ -355,7 +376,11 @@ export async function POST(request: Request) {
|
||||
|
||||
const reviewImageCandidates = [imageUrl, ...referenceImages].slice(0, 6);
|
||||
let effectiveReviewImages = reviewImageCandidates;
|
||||
if (!isVolcanoResponsesReviewer(reviewerConfig)) {
|
||||
if (
|
||||
!localDetailAgentResolved &&
|
||||
reviewerConfig &&
|
||||
!isVolcanoResponsesReviewer(reviewerConfig)
|
||||
) {
|
||||
const { inlineDataUrls } = await inlineVisionImages(reviewImageCandidates, {
|
||||
maxImages: 6,
|
||||
maxSide: 900,
|
||||
@ -409,9 +434,9 @@ export async function POST(request: Request) {
|
||||
uiPatch: undefined,
|
||||
uiSurfacePatch: undefined,
|
||||
model: {
|
||||
id: reviewerConfig.model.id,
|
||||
displayName: reviewerConfig.model.displayName,
|
||||
providerType: reviewerConfig.provider.type,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
providerType: selectedProviderRef.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
@ -565,9 +590,9 @@ export async function POST(request: Request) {
|
||||
issues: uiValidation.issues.map((item) => item.message),
|
||||
},
|
||||
model: {
|
||||
id: reviewerConfig.model.id,
|
||||
displayName: reviewerConfig.model.displayName,
|
||||
providerType: reviewerConfig.provider.type,
|
||||
id: selectedModelRef.id,
|
||||
displayName: selectedModelRef.displayName,
|
||||
providerType: selectedProviderRef.type,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
41
lib/ai/detail-agent-model-config.ts
Normal file
41
lib/ai/detail-agent-model-config.ts
Normal file
@ -0,0 +1,41 @@
|
||||
export const DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY =
|
||||
"canvas_detail_agent_moonshot_config";
|
||||
export const DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID =
|
||||
"__moonshot_kimi_k25_local__";
|
||||
export const DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL =
|
||||
"https://api.moonshot.cn/v1";
|
||||
export const DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID = "kimi-k2.5";
|
||||
|
||||
export type DetailAgentLocalMoonshotConfig = {
|
||||
enabled: boolean;
|
||||
providerName: string;
|
||||
baseUrl: string;
|
||||
apiKey: string;
|
||||
modelId: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
export function isDetailAgentLocalMoonshotOption(value: unknown) {
|
||||
return String(value || "").trim() === DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID;
|
||||
}
|
||||
|
||||
export function normalizeDetailAgentLocalMoonshotConfig(
|
||||
value: unknown,
|
||||
): DetailAgentLocalMoonshotConfig {
|
||||
const raw =
|
||||
value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as Record<string, unknown>)
|
||||
: {};
|
||||
|
||||
return {
|
||||
enabled: raw.enabled !== false,
|
||||
providerName:
|
||||
String(raw.providerName || "").trim() || "Moonshot AI (Official)",
|
||||
baseUrl:
|
||||
String(raw.baseUrl || "").trim() || DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
|
||||
apiKey: String(raw.apiKey || "").trim(),
|
||||
modelId:
|
||||
String(raw.modelId || "").trim() || DETAIL_AGENT_LOCAL_MOONSHOT_MODEL_ID,
|
||||
displayName: String(raw.displayName || "").trim() || "官方 Kimi K2.5",
|
||||
};
|
||||
}
|
||||
73
lib/ai/detail-agent-model-runtime.ts
Normal file
73
lib/ai/detail-agent-model-runtime.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import "server-only";
|
||||
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import type { LanguageModel } from "ai";
|
||||
import { getSystemSetting } from "@/lib/db/queries";
|
||||
import {
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY,
|
||||
isDetailAgentLocalMoonshotOption,
|
||||
normalizeDetailAgentLocalMoonshotConfig,
|
||||
} from "@/lib/ai/detail-agent-model-config";
|
||||
|
||||
export async function resolveDetailAgentLocalMoonshotModel(params: {
|
||||
siteSettingModelId?: string | null;
|
||||
}) {
|
||||
if (!isDetailAgentLocalMoonshotOption(params.siteSettingModelId)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const setting = await getSystemSetting({
|
||||
key: DETAIL_AGENT_LOCAL_MOONSHOT_SETTING_KEY,
|
||||
}).catch(() => null);
|
||||
const config = normalizeDetailAgentLocalMoonshotConfig(setting?.value);
|
||||
|
||||
if (!config.enabled || !config.apiKey.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const provider = createOpenAI({
|
||||
apiKey: config.apiKey,
|
||||
baseURL: config.baseUrl || DETAIL_AGENT_LOCAL_MOONSHOT_BASE_URL,
|
||||
});
|
||||
const model = provider.chat(config.modelId);
|
||||
|
||||
return {
|
||||
kind: "local_moonshot" as const,
|
||||
model,
|
||||
modelRef: {
|
||||
id: DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
displayName: config.displayName,
|
||||
modelId: config.modelId,
|
||||
supportsVision: true,
|
||||
capabilities: { multimodalModel: true },
|
||||
defaultParams: null,
|
||||
},
|
||||
providerRef: {
|
||||
id: DETAIL_AGENT_LOCAL_MOONSHOT_OPTION_ID,
|
||||
name: config.providerName,
|
||||
type: "openai",
|
||||
baseUrl: config.baseUrl,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type DetailAgentResolvedModel = {
|
||||
kind: "local_moonshot" | "db_model";
|
||||
model: LanguageModel;
|
||||
modelRef: {
|
||||
id: string;
|
||||
displayName: string;
|
||||
modelId: string;
|
||||
supportsVision?: boolean | null;
|
||||
capabilities?: unknown;
|
||||
defaultParams?: unknown;
|
||||
};
|
||||
providerRef: {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
baseUrl?: string | null;
|
||||
};
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user