149 lines
3.8 KiB
TypeScript
149 lines
3.8 KiB
TypeScript
import { z } from "zod";
|
|
import { getAiProviderById } from "@/lib/db/queries";
|
|
import { ChatSDKError } from "@/lib/errors";
|
|
import { KittyCadApiError, kittyCadRequest } from "@/lib/kittycad/client";
|
|
import { getKittyCadProviderConfig } from "@/lib/kittycad/server";
|
|
import { requireAdmin } from "@/lib/middleware/admin";
|
|
|
|
export const runtime = "nodejs";
|
|
|
|
const requestSchema = z.object({
|
|
config: z
|
|
.object({
|
|
providerId: z.string().uuid().optional(),
|
|
name: z.string().trim().optional(),
|
|
baseUrl: z.string().trim().optional(),
|
|
apiKey: z.string().trim().optional(),
|
|
})
|
|
.optional(),
|
|
});
|
|
|
|
function pickDisplayName(user: Record<string, unknown>) {
|
|
const candidates = [
|
|
user.name,
|
|
user.full_name,
|
|
user.display_name,
|
|
user.username,
|
|
user.first_name,
|
|
];
|
|
|
|
for (const value of candidates) {
|
|
if (typeof value === "string" && value.trim()) {
|
|
return value.trim();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function pickEmail(user: Record<string, unknown>) {
|
|
const candidates = [user.email, user.primary_email];
|
|
|
|
for (const value of candidates) {
|
|
if (typeof value === "string" && value.trim()) {
|
|
return value.trim();
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async function resolveKittyCadConfig(
|
|
input?: {
|
|
providerId?: string;
|
|
name?: string;
|
|
baseUrl?: string;
|
|
apiKey?: string;
|
|
} | null
|
|
) {
|
|
const providerId = String(input?.providerId || "").trim();
|
|
const inputApiKey = String(input?.apiKey || "").trim();
|
|
const inputBaseUrl = String(input?.baseUrl || "").trim();
|
|
const inputName = String(input?.name || "").trim();
|
|
|
|
if (providerId) {
|
|
const savedProvider = await getAiProviderById({ id: providerId });
|
|
if (!savedProvider) {
|
|
throw new Error("未找到对应的 KittyCAD CAD 提供商");
|
|
}
|
|
|
|
return {
|
|
apiKey: inputApiKey || String(savedProvider.apiKey || "").trim(),
|
|
baseUrl: inputBaseUrl || String(savedProvider.baseUrl || "").trim(),
|
|
providerId: savedProvider.id,
|
|
providerName: inputName || savedProvider.name || "KittyCAD CAD",
|
|
source: "provider" as const,
|
|
};
|
|
}
|
|
|
|
if (inputApiKey) {
|
|
return {
|
|
apiKey: inputApiKey,
|
|
baseUrl: inputBaseUrl || "https://api.zoo.dev",
|
|
providerName: inputName || "KittyCAD CAD",
|
|
source: "provider" as const,
|
|
};
|
|
}
|
|
|
|
return await getKittyCadProviderConfig();
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
try {
|
|
await requireAdmin();
|
|
|
|
const body = requestSchema.parse(await request.json().catch(() => ({})));
|
|
const config = await resolveKittyCadConfig(body.config);
|
|
|
|
if (!config.apiKey.trim()) {
|
|
throw new Error("请填写 KittyCAD API Token");
|
|
}
|
|
|
|
const startedAt = Date.now();
|
|
const user = await kittyCadRequest<Record<string, unknown>>(config, {
|
|
method: "GET",
|
|
path: "/user",
|
|
});
|
|
const latencyMs = Date.now() - startedAt;
|
|
|
|
return Response.json({
|
|
success: true,
|
|
providerName: config.providerName || "KittyCAD CAD",
|
|
baseUrl: config.baseUrl,
|
|
latencyMs,
|
|
userId:
|
|
typeof user.id === "string" && user.id.trim() ? user.id.trim() : null,
|
|
userName: pickDisplayName(user),
|
|
userEmail: pickEmail(user),
|
|
});
|
|
} catch (error) {
|
|
if (error instanceof ChatSDKError) {
|
|
return error.toResponse();
|
|
}
|
|
|
|
if (error instanceof KittyCadApiError) {
|
|
return Response.json(
|
|
{
|
|
code: "bad_request:api",
|
|
message: error.message,
|
|
payload: error.payload,
|
|
},
|
|
{ status: error.httpStatus || 400 }
|
|
);
|
|
}
|
|
|
|
if (error instanceof z.ZodError) {
|
|
return new ChatSDKError("bad_request:api", error.message).toResponse();
|
|
}
|
|
|
|
return Response.json(
|
|
{
|
|
code: "bad_request:api",
|
|
message:
|
|
error instanceof Error ? error.message : "KittyCAD 连接测试失败",
|
|
},
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
}
|