diff --git a/app/api/canvas/generate/image/route.ts b/app/api/canvas/generate/image/route.ts index 9b19e28c0..d51b21610 100644 --- a/app/api/canvas/generate/image/route.ts +++ b/app/api/canvas/generate/image/route.ts @@ -26,6 +26,10 @@ function toGenerateImagePayload(body: GenerateImageRequest): CanvasGenerateImage typeof body.aspectRatio === "string" && body.aspectRatio.trim() ? body.aspectRatio.trim() : "1:1", + imageSize: + typeof body.imageSize === "string" && body.imageSize.trim() + ? body.imageSize.trim() + : undefined, imageCount: typeof body.imageCount === "number" && Number.isFinite(body.imageCount) ? body.imageCount diff --git a/components/canvas/CanvasChatPanel.tsx b/components/canvas/CanvasChatPanel.tsx index 11c2af1c3..c9de01e87 100644 --- a/components/canvas/CanvasChatPanel.tsx +++ b/components/canvas/CanvasChatPanel.tsx @@ -343,6 +343,7 @@ type CanvasImageGenerationRequest = { prompt: string; model?: string; aspectRatio?: string; + imageSize?: string; imageCount?: number; negativePrompt?: string; referenceImage?: string; @@ -4162,6 +4163,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF prompt: promptOverride, model: modelForGeneration, aspectRatio: "9:16", + imageSize: imageParams.imageSize, imageCount: 1, negativePrompt: imageParams.negativePrompt, referenceImage: productAttachments[0]?.url, @@ -4421,6 +4423,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF prompt: reviewPrompt, model: modelForGeneration, aspectRatio: "1:1", + imageSize: imageParams.imageSize, imageCount: DETAIL_REVIEW_IMAGE_COUNT, negativePrompt: imageParams.negativePrompt, referenceImage: productAttachments[0]?.url, @@ -4770,6 +4773,7 @@ export function CanvasChatPanel({ chatId, initialMessages = [], onClose, onOpenF prompt: reviewPrompt, model: modelForGeneration, aspectRatio: "1:1", + imageSize: imageParams.imageSize, imageCount: DETAIL_REVIEW_IMAGE_COUNT, negativePrompt: imageParams.negativePrompt, referenceImage: productAttachments[0]?.url, diff --git a/lib/ai/providers/ruxa-image.ts b/lib/ai/providers/ruxa-image.ts index 8fc706c81..dac1de838 100644 --- a/lib/ai/providers/ruxa-image.ts +++ b/lib/ai/providers/ruxa-image.ts @@ -75,13 +75,21 @@ function normalizeOutputFormat(outputFormat?: string): "png" | "jpg" { } function extractTaskId(payload: any): string | undefined { + if (typeof payload === "string" && payload.trim()) { + return payload.trim(); + } + if (typeof payload?.data === "string" && payload.data.trim()) { + return payload.data.trim(); + } return ( payload?.taskId || payload?.task_id || payload?.id || + payload?.task?.id || payload?.data?.taskId || payload?.data?.task_id || - payload?.data?.id + payload?.data?.id || + payload?.data?.task?.id ); } @@ -110,6 +118,15 @@ function extractErrorMessage(payload: any): string { ); } +function compactPayloadSnippet(payload: any): string { + try { + const json = JSON.stringify(payload); + return json.length > 320 ? `${json.slice(0, 320)}...` : json; + } catch { + return String(payload || ""); + } +} + function extractPromptAndImages(messages: Array<{ role: string; content: any }>) { const lastUserMessage = messages.filter((message) => message.role === "user").pop(); if (!lastUserMessage) { @@ -347,34 +364,46 @@ export function createRuxaImage({ apiKey, baseURL }: RuxaImageConfig) { const activeModelId = options.overrideModelId || modelId; const createEndpoint = joinApiPath(baseURL, "/api/v1/tasks/create"); + const callbackUrl = process.env.RUXA_CALLBACK_URL?.trim(); + const requestBody = { + input: { + prompt, + image_input: imageUrls, + aspect_ratio: aspectRatio, + resolution, + output_format: outputFormat, + }, + model: activeModelId, + ...(callbackUrl ? { callBackUrl: callbackUrl } : {}), + }; const createResponse = await fetch(createEndpoint, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, - body: JSON.stringify({ - input: { - prompt, - image_input: imageUrls, - aspect_ratio: aspectRatio, - resolution, - output_format: outputFormat, - }, - model: activeModelId, - }), + body: JSON.stringify(requestBody), }); - - const createPayload = await createResponse.json(); + const createRawText = await createResponse.text(); + let createPayload: any = null; + try { + createPayload = JSON.parse(createRawText); + } catch { + createPayload = createRawText; + } if (!createResponse.ok) { - throw new Error(extractErrorMessage(createPayload)); + throw new Error( + `${extractErrorMessage(createPayload)} (HTTP ${createResponse.status})` + ); } let outputUrls = filterSourceImages(collectImageUrls(createPayload), imageUrls); if (outputUrls.length === 0) { const taskId = extractTaskId(createPayload); if (!taskId) { - throw new Error("RUXA 创建任务成功,但未返回 taskId"); + throw new Error( + `RUXA 创建任务成功,但未返回 taskId,响应片段: ${compactPayloadSnippet(createPayload)}` + ); } outputUrls = await waitForTaskResult(baseURL, apiKey, taskId, imageUrls); } diff --git a/lib/canvas/generate-image.ts b/lib/canvas/generate-image.ts index dbb6b0344..c2ede07fa 100644 --- a/lib/canvas/generate-image.ts +++ b/lib/canvas/generate-image.ts @@ -12,6 +12,7 @@ export interface CanvasGenerateImageRequest { negativePrompt?: string; model?: string; aspectRatio?: string; + imageSize?: string; imageCount?: number; referenceImage?: string; referenceImages?: string[]; @@ -80,6 +81,10 @@ export async function generateCanvasImage( typeof request.aspectRatio === "string" && request.aspectRatio.trim() ? request.aspectRatio.trim() : "1:1"; + const imageSize = + typeof request.imageSize === "string" && request.imageSize.trim() + ? request.imageSize.trim() + : "2K"; const imageCount = typeof request.imageCount === "number" && Number.isFinite(request.imageCount) ? Math.max(1, Math.min(8, Math.floor(request.imageCount))) @@ -180,7 +185,7 @@ export async function generateCanvasImage( const imageParams = { aspectRatio, - imageSize: "2K", + imageSize, imageCount, negativePrompt, };