196 lines
5.4 KiB
TypeScript
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,
|
|
};
|
|
}
|