Your Name 413b5ffac1
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add AI-CAD workspace and KittyCAD integration
2026-04-06 14:46:51 +08:00

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 }
);
}
}