diff --git a/app/api/ai-cad/tasks/[taskId]/route.ts b/app/api/ai-cad/tasks/[taskId]/route.ts index a61dbaa3d..b745a4fda 100644 --- a/app/api/ai-cad/tasks/[taskId]/route.ts +++ b/app/api/ai-cad/tasks/[taskId]/route.ts @@ -19,6 +19,10 @@ type KittyCadTaskResponse = { output_format?: string | null; }; +type KittyCadAsyncOperationResponse = KittyCadTaskResponse & { + type?: string | null; +}; + type ArchivedFile = { sourceHash: string; archivedUrl: string; @@ -134,6 +138,78 @@ function pickPreviewImageFile(files: Array<{ name: string; url: string }>) { ); } +function extractOutputEntries(task: KittyCadTaskResponse) { + return Object.entries(task.outputs || {}).filter( + (entry): entry is [string, string] => + typeof entry[0] === "string" && + entry[0].trim().length > 0 && + typeof entry[1] === "string" && + entry[1].trim().length > 0 + ); +} + +async function enrichTextToCadOutputs(params: { + config: Awaited>; + task: KittyCadTaskResponse; + taskId: string; +}) { + if (extractOutputEntries(params.task).length > 0) { + return params.task; + } + + try { + const asyncOperation = await kittyCadRequest( + params.config, + { + method: "GET", + path: `/async/operations/${encodeURIComponent(params.taskId)}`, + } + ); + + if (extractOutputEntries(asyncOperation).length === 0) { + return params.task; + } + + return { + ...params.task, + ...asyncOperation, + code: asyncOperation.code || params.task.code || null, + conversation_id: + asyncOperation.conversation_id || params.task.conversation_id || null, + model_version: + asyncOperation.model_version || params.task.model_version || null, + output_format: + asyncOperation.output_format || params.task.output_format || null, + outputs: asyncOperation.outputs, + prompt: asyncOperation.prompt || params.task.prompt || null, + } satisfies KittyCadTaskResponse; + } catch (error) { + console.warn("[KittyCADStatus] async output fallback failed", { + taskId: params.taskId, + error: error instanceof Error ? error.message : String(error), + }); + return params.task; + } +} + +function createClientSafeTaskOutput(task: KittyCadTaskResponse) { + const outputEntries = extractOutputEntries(task); + const outputSummary = Object.fromEntries( + outputEntries.map(([name, base64]) => [ + name, + { + base64Length: base64.length, + extension: getFileExtension(name) || null, + }, + ]) + ); + + return { + ...task, + outputs: outputSummary, + }; +} + async function archiveOutputIfNeeded(params: { taskId: string; filename: string; @@ -269,7 +345,7 @@ export async function GET(_request: Request, { params }: RouteParams) { } const config = await getKittyCadProviderConfig(); - const result = await kittyCadRequest(config, { + let result = await kittyCadRequest(config, { method: "GET", path: `/user/text-to-cad/${encodeURIComponent(normalizedTaskId)}`, }); @@ -287,18 +363,18 @@ export async function GET(_request: Request, { params }: RouteParams) { conversationId: result.conversation_id || null, modelVersion: result.model_version || null, outputFormat: result.output_format || null, - output: result, + output: createClientSafeTaskOutput(result), }, }); } - const outputEntries = Object.entries(result.outputs || {}).filter( - (entry): entry is [string, string] => - typeof entry[0] === "string" && - entry[0].trim().length > 0 && - typeof entry[1] === "string" && - entry[1].trim().length > 0 - ); + result = await enrichTextToCadOutputs({ + config, + task: result, + taskId: normalizedTaskId, + }); + + const outputEntries = extractOutputEntries(result); const archivedEntries = await Promise.all( outputEntries.map(async ([name, base64]) => { @@ -365,7 +441,7 @@ export async function GET(_request: Request, { params }: RouteParams) { outputFormat: result.output_format || null, codeUrl: codeFile?.url || null, downloadUrls, - output: result, + output: createClientSafeTaskOutput(result), }, }); } catch (error) {