Your Name d58b706799
Some checks failed
Lint / build (20) (push) Has been cancelled
security: implement Phase 1 & partial Phase 2 improvements
- 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
2026-05-05 22:49:57 +08:00

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