Some checks failed
Lint / build (20) (push) Has been cancelled
- Remove hardcoded admin password, use env var or auto-generate - Fix trustHost to be conditional (Vercel-only by default) - Add HTTP security headers (CSP, X-Frame-Options, HSTS) - Create middleware.ts to activate existing rate limiting - Implement structured logging system - Rewrite error handling in ai-cad service - Update security documentation
277 lines
7.7 KiB
TypeScript
277 lines
7.7 KiB
TypeScript
import type { Model3DTaskStatus } from "@/services/canvas";
|
|
import { createLogger } from "@/lib/logger";
|
|
|
|
const log = createLogger("AiCad");
|
|
|
|
type ApiResponse<T = any> = {
|
|
success: boolean;
|
|
data?: T;
|
|
error?: string;
|
|
};
|
|
|
|
export type AiCadProviderStatus = {
|
|
configured: boolean;
|
|
source: "provider" | "env" | "none";
|
|
providerName?: string | null;
|
|
baseUrl?: string | null;
|
|
};
|
|
|
|
export type AiCadWorkspaceSummary = {
|
|
id: string;
|
|
name: string;
|
|
generationProvider: "kittycad-cad" | "tripo-3d";
|
|
versionCount: number;
|
|
completedCount: number;
|
|
hasPendingTasks: boolean;
|
|
latestPrompt?: string | null;
|
|
createdAt?: string | null;
|
|
updatedAt?: string | null;
|
|
};
|
|
|
|
export type AiCadWorkspaceDocument = {
|
|
id: string;
|
|
name: string;
|
|
prompt: string;
|
|
generationProvider: "kittycad-cad" | "tripo-3d";
|
|
modelVersion: string;
|
|
viewerSettings: Record<string, unknown>;
|
|
messages: Record<string, unknown>[];
|
|
versions: Record<string, unknown>[];
|
|
activeVersionId?: string | null;
|
|
createdAt?: string | null;
|
|
updatedAt?: string | null;
|
|
};
|
|
|
|
export type AiCadModelingSession = {
|
|
sessionId: string;
|
|
createdAt?: string | null;
|
|
};
|
|
|
|
export type AiCadModelingCommandPayload = {
|
|
requestId?: string | null;
|
|
responseType?: string | null;
|
|
responseData?: Record<string, any> | null;
|
|
snapshotUrl?: string | null;
|
|
snapshotError?: string | null;
|
|
raw?: any;
|
|
};
|
|
|
|
function handleError(
|
|
err: unknown,
|
|
context: string,
|
|
fallbackMessage: string,
|
|
extra?: Record<string, unknown>
|
|
): ApiResponse<never> {
|
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
log.error(`${context} failed`, error, extra);
|
|
return { success: false, error: fallbackMessage };
|
|
}
|
|
|
|
export const aiCadGenerationService = {
|
|
async listWorkspaces(): Promise<ApiResponse<AiCadWorkspaceSummary[]>> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/workspaces");
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "listWorkspaces", "读取 AI-CAD 工作台列表失败");
|
|
}
|
|
},
|
|
|
|
async createWorkspace(params?: {
|
|
name?: string;
|
|
}): Promise<ApiResponse<AiCadWorkspaceSummary>> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/workspaces", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(params || {}),
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "createWorkspace", "创建 AI-CAD 工作台失败", { name: params?.name });
|
|
}
|
|
},
|
|
|
|
async getWorkspace(
|
|
workspaceId: string
|
|
): Promise<ApiResponse<AiCadWorkspaceDocument | null>> {
|
|
try {
|
|
const response = await fetch(`/api/ai-cad/workspaces/${workspaceId}`);
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "getWorkspace", "读取 AI-CAD 工作台失败", { workspaceId });
|
|
}
|
|
},
|
|
|
|
async saveWorkspace(
|
|
workspaceId: string,
|
|
params: {
|
|
name?: string;
|
|
prompt: string;
|
|
generationProvider: "kittycad-cad" | "tripo-3d";
|
|
modelVersion: string;
|
|
viewerSettings: Record<string, unknown>;
|
|
messages: Record<string, unknown>[];
|
|
versions: Record<string, unknown>[];
|
|
activeVersionId?: string | null;
|
|
}
|
|
): Promise<
|
|
ApiResponse<{
|
|
id: string;
|
|
name: string;
|
|
updatedAt?: string | null;
|
|
summary?: AiCadWorkspaceSummary;
|
|
}>
|
|
> {
|
|
try {
|
|
const response = await fetch(`/api/ai-cad/workspaces/${workspaceId}`, {
|
|
method: "PUT",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(params),
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "saveWorkspace", "保存 AI-CAD 工作台失败", { workspaceId });
|
|
}
|
|
},
|
|
|
|
async deleteWorkspace(
|
|
workspaceId: string
|
|
): Promise<ApiResponse<{ id: string }>> {
|
|
try {
|
|
const response = await fetch(`/api/ai-cad/workspaces/${workspaceId}`, {
|
|
method: "DELETE",
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "deleteWorkspace", "删除 AI-CAD 工作台失败", { workspaceId });
|
|
}
|
|
},
|
|
|
|
async getProviderStatus(): Promise<ApiResponse<AiCadProviderStatus>> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/provider");
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "getProviderStatus", "读取 AI-CAD 提供商状态失败");
|
|
}
|
|
},
|
|
|
|
async createTask(params: {
|
|
prompt?: string;
|
|
outputFormat?: string;
|
|
projectName?: string;
|
|
iterationSourceTaskId?: string;
|
|
sourceCode?: string;
|
|
sourceRanges?: Record<string, unknown>[];
|
|
}): Promise<
|
|
ApiResponse<{
|
|
taskId: string;
|
|
status: Model3DTaskStatus;
|
|
conversationId?: string | null;
|
|
modelVersion?: string | null;
|
|
outputFormat?: string | null;
|
|
generationMode?: "create" | "iterate";
|
|
sourceTaskId?: string | null;
|
|
raw?: any;
|
|
}>
|
|
> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/tasks", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(params),
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "createTask", "创建 AI-CAD 任务失败", {
|
|
outputFormat: params.outputFormat,
|
|
});
|
|
}
|
|
},
|
|
|
|
async createModelingSession(): Promise<ApiResponse<AiCadModelingSession>> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/modeling/session", {
|
|
method: "POST",
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "createModelingSession", "创建官方建模会话失败");
|
|
}
|
|
},
|
|
|
|
async closeModelingSession(
|
|
sessionId: string
|
|
): Promise<ApiResponse<{ sessionId: string }>> {
|
|
try {
|
|
const response = await fetch(
|
|
`/api/ai-cad/modeling/session/${encodeURIComponent(sessionId)}`,
|
|
{
|
|
method: "DELETE",
|
|
}
|
|
);
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "closeModelingSession", "关闭官方建模会话失败", { sessionId });
|
|
}
|
|
},
|
|
|
|
async sendModelingCommand(params: {
|
|
sessionId: string;
|
|
cmd: Record<string, unknown>;
|
|
cmdId?: string;
|
|
captureSnapshot?: boolean;
|
|
snapshotFormat?: "jpeg" | "png";
|
|
}): Promise<ApiResponse<AiCadModelingCommandPayload>> {
|
|
try {
|
|
const response = await fetch("/api/ai-cad/modeling/command", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(params),
|
|
});
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "sendModelingCommand", "发送官方建模命令失败", {
|
|
sessionId: params.sessionId,
|
|
});
|
|
}
|
|
},
|
|
|
|
async getTask(taskId: string): Promise<
|
|
ApiResponse<{
|
|
taskId: string;
|
|
type: string;
|
|
status: Model3DTaskStatus;
|
|
progress?: number | null;
|
|
output?: Record<string, any>;
|
|
modelUrl?: string | null;
|
|
previewImageUrl?: string | null;
|
|
errorMessage?: string | null;
|
|
conversationId?: string | null;
|
|
modelVersion?: string | null;
|
|
outputFormat?: string | null;
|
|
codeUrl?: string | null;
|
|
downloadUrls?: Record<string, string>;
|
|
}>
|
|
> {
|
|
try {
|
|
const response = await fetch(`/api/ai-cad/tasks/${taskId}`);
|
|
const data = await response.json();
|
|
return data;
|
|
} catch (err) {
|
|
return handleError(err, "getTask", "查询 AI-CAD 任务失败", { taskId });
|
|
}
|
|
},
|
|
};
|