ai-chatbot/app/api/ai-cad/workspace-utils.ts
Your Name a26e76638e
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
feat: add ai-cad camera orientation cube
2026-04-07 12:14:57 +08:00

196 lines
5.4 KiB
TypeScript

import type { AiCadWorkspace } from "@/lib/db/schema";
type CadGenerationProvider = "kittycad-cad" | "tripo-3d";
const FINAL_TASK_STATUS = new Set([
"success",
"failed",
"banned",
"expired",
"cancelled",
"unknown",
]);
const DEFAULT_VIEWER_SETTINGS = {
cameraPreset: "iso",
envPreset: "studio",
lightIntensity: 1.1,
autoRotate: true,
showGrid: true,
wireframe: false,
} as const;
type WorkspaceVersionLike = {
status?: unknown;
prompt?: unknown;
};
export type UpdateAiCadWorkspaceRequest = {
name?: string;
prompt?: string;
generationProvider?: string;
modelVersion?: string;
viewerSettings?: unknown;
messages?: unknown;
versions?: unknown;
activeVersionId?: string | null;
};
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
function getWorkspaceVersions(value: unknown): WorkspaceVersionLike[] {
if (!Array.isArray(value)) {
return [];
}
return value.filter((item): item is WorkspaceVersionLike => isRecord(item));
}
export function normalizeViewerSettings(
value: unknown
): Record<string, unknown> {
if (!isRecord(value)) {
return { ...DEFAULT_VIEWER_SETTINGS };
}
return {
cameraPreset:
value.cameraPreset === "top" ||
value.cameraPreset === "front" ||
value.cameraPreset === "right" ||
value.cameraPreset === "iso"
? value.cameraPreset
: DEFAULT_VIEWER_SETTINGS.cameraPreset,
envPreset:
typeof value.envPreset === "string" && value.envPreset.trim()
? value.envPreset
: DEFAULT_VIEWER_SETTINGS.envPreset,
lightIntensity:
typeof value.lightIntensity === "number" &&
Number.isFinite(value.lightIntensity)
? value.lightIntensity
: DEFAULT_VIEWER_SETTINGS.lightIntensity,
autoRotate:
typeof value.autoRotate === "boolean"
? value.autoRotate
: DEFAULT_VIEWER_SETTINGS.autoRotate,
showGrid:
typeof value.showGrid === "boolean"
? value.showGrid
: DEFAULT_VIEWER_SETTINGS.showGrid,
wireframe:
typeof value.wireframe === "boolean"
? value.wireframe
: DEFAULT_VIEWER_SETTINGS.wireframe,
};
}
export function normalizeJsonRecordArray(
value: unknown
): Record<string, unknown>[] {
if (!Array.isArray(value)) {
return [];
}
return value.filter((item): item is Record<string, unknown> =>
isRecord(item)
);
}
export function normalizeWorkspaceName(
value: unknown,
fallbackName = "AI-CAD 工作台"
) {
return typeof value === "string" && value.trim()
? value.trim()
: fallbackName;
}
export function normalizeWorkspacePayload(body: UpdateAiCadWorkspaceRequest): {
name?: string;
prompt: string;
generationProvider: CadGenerationProvider;
modelVersion: string;
viewerSettings: Record<string, unknown>;
messages: Record<string, unknown>[];
versions: Record<string, unknown>[];
activeVersionId: string | null;
} {
return {
...(typeof body.name === "string" && body.name.trim()
? { name: body.name.trim() }
: {}),
prompt: String(body.prompt || ""),
generationProvider:
body.generationProvider === "tripo-3d" ? "tripo-3d" : "kittycad-cad",
modelVersion:
typeof body.modelVersion === "string" && body.modelVersion.trim()
? body.modelVersion
: "Turbo-v1.0-20250506",
viewerSettings: normalizeViewerSettings(body.viewerSettings),
messages: normalizeJsonRecordArray(body.messages),
versions: normalizeJsonRecordArray(body.versions),
activeVersionId:
typeof body.activeVersionId === "string" && body.activeVersionId.trim()
? body.activeVersionId
: null,
};
}
export function buildDefaultWorkspaceName(index: number) {
return index > 1 ? `AI-CAD 工作台 ${index}` : "AI-CAD 工作台";
}
export function createWorkspaceSummary(workspace: AiCadWorkspace) {
const versions = getWorkspaceVersions(workspace.versions);
const versionCount = versions.length;
const completedCount = versions.filter(
(version) => version.status === "success"
).length;
const hasPendingTasks = versions.some(
(version) =>
typeof version.status !== "string" ||
!FINAL_TASK_STATUS.has(version.status)
);
const latestPrompt =
typeof workspace.prompt === "string" && workspace.prompt.trim()
? workspace.prompt.trim()
: versions.find(
(version) =>
typeof version.prompt === "string" && version.prompt.trim()
)?.prompt || null;
return {
id: workspace.id,
name: workspace.name,
generationProvider: workspace.generationProvider,
versionCount,
completedCount,
hasPendingTasks,
latestPrompt:
typeof latestPrompt === "string" && latestPrompt.trim()
? latestPrompt.trim()
: null,
createdAt: workspace.createdAt?.toISOString() || null,
updatedAt: workspace.updatedAt?.toISOString() || null,
};
}
export function createWorkspaceDocument(workspace: AiCadWorkspace) {
return {
id: workspace.id,
name: workspace.name,
prompt: workspace.prompt,
generationProvider: workspace.generationProvider,
modelVersion: workspace.modelVersion,
viewerSettings: workspace.viewerSettings,
messages: workspace.messages,
versions: workspace.versions,
activeVersionId: workspace.activeVersionId,
createdAt: workspace.createdAt?.toISOString() || null,
updatedAt: workspace.updatedAt?.toISOString() || null,
};
}