fix(realtime-canvas): use runware seedImage-compatible model
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-02-25 15:48:16 +08:00
parent 6c30ab90ef
commit 80e826039f
3 changed files with 75 additions and 43 deletions

View File

@ -34,7 +34,7 @@ const defaultConfig = {
jpegQuality: 0.55,
},
model: {
id: "runware:400@4",
id: "runware:101@1",
defaults: {
steps: 4,
cfgScale: 3.5,

View File

@ -108,7 +108,10 @@ export async function POST(request: Request) {
);
}
const model = String(config.runwareModel || process.env.RUNWARE_MODEL || "runware:400@4").trim();
const fallbackSeedImageModel = "runware:101@1";
const model = String(
config.runwareModel || process.env.RUNWARE_MODEL || fallbackSeedImageModel
).trim();
const maxInputResolution = clamp(Number(config.maxInputResolution || 1024), 512, 1536);
const size = resolveSquareSize(body.imageSize, maxInputResolution);
const steps = clamp(
@ -127,56 +130,85 @@ export async function POST(request: Request) {
1
);
const taskUUID = crypto.randomUUID();
const runwareRequest = [
{
taskType: "imageInference",
const positivePrompt =
String(body.prompt || "").trim() || "将草图渲染为高质感产品设计图,保持结构和主轮廓一致。";
const requestInference = async (modelId: string) => {
const taskUUID = crypto.randomUUID();
const runwareRequest = [
{
taskType: "imageInference",
taskUUID,
model: modelId,
positivePrompt,
seedImage: imageUrl,
width: size,
height: size,
strength,
CFGScale: cfgScale,
steps,
numberResults: 1,
outputType: "dataURI",
outputFormat: "JPG",
},
];
const upstreamResponse = await fetch("https://api.runware.ai/v1", {
method: "POST",
headers: {
Authorization: `Bearer ${runwareApiKey}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(runwareRequest),
cache: "no-store",
});
const upstreamJson = await upstreamResponse.json().catch(() => null);
const upstreamError = parseRunwareError(upstreamJson);
return {
taskUUID,
model,
positivePrompt: String(body.prompt || "").trim() || "将草图渲染为高质感产品设计图,保持结构和主轮廓一致。",
seedImage: imageUrl,
width: size,
height: size,
strength,
CFGScale: cfgScale,
steps,
numberResults: 1,
outputType: "dataURI",
outputFormat: "JPG",
},
];
modelId,
upstreamResponse,
upstreamJson,
upstreamError,
};
};
const upstreamResponse = await fetch("https://api.runware.ai/v1", {
method: "POST",
headers: {
Authorization: `Bearer ${runwareApiKey}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify(runwareRequest),
cache: "no-store",
});
let requestResult = await requestInference(model);
const shouldRetryWithSeedImageModel =
(requestResult.upstreamError || "")
.toLowerCase()
.includes("unsupported use of 'seedimage' parameter") &&
model !== fallbackSeedImageModel;
const upstreamJson = await upstreamResponse.json().catch(() => null);
const upstreamError = parseRunwareError(upstreamJson);
if (shouldRetryWithSeedImageModel) {
requestResult = await requestInference(fallbackSeedImageModel);
}
if (!upstreamResponse.ok || upstreamError) {
if (!requestResult.upstreamResponse.ok || requestResult.upstreamError) {
return Response.json(
{
code: "RUNWARE_REQUEST_FAILED",
message: upstreamError || `Runware 请求失败(${upstreamResponse.status})`,
message:
requestResult.upstreamError ||
`Runware 请求失败(${requestResult.upstreamResponse.status})`,
},
{ status: 502 }
);
}
const resultList = Array.isArray(upstreamJson)
? upstreamJson
: Array.isArray(upstreamJson?.data)
? upstreamJson.data
const resultList = Array.isArray(requestResult.upstreamJson)
? requestResult.upstreamJson
: Array.isArray(requestResult.upstreamJson?.data)
? requestResult.upstreamJson.data
: [];
const imageResult =
resultList.find((item: any) => item?.taskUUID === taskUUID && (item?.imageDataURI || item?.imageURL)) ||
resultList.find(
(item: any) =>
item?.taskUUID === requestResult.taskUUID && (item?.imageDataURI || item?.imageURL)
) ||
resultList.find((item: any) => item?.imageDataURI || item?.imageURL);
const imageDataURI = String(imageResult?.imageDataURI || "").trim();
@ -194,16 +226,16 @@ export async function POST(request: Request) {
console.log("[RealtimeRunware] frame success", {
userId: session.user.id,
requestId: body.requestId || null,
taskUUID,
model,
taskUUID: requestResult.taskUUID,
model: requestResult.modelId,
cost,
});
return Response.json({
ok: true,
provider: "runware",
model,
taskUUID,
model: requestResult.modelId,
taskUUID: requestResult.taskUUID,
imageDataURI: imageDataURI || undefined,
imageUrl: imageURL || undefined,
cost,

View File

@ -89,7 +89,7 @@ const defaultRealtimeConfig: RealtimeConfig = {
jpegQuality: 0.55,
},
model: {
id: "runware:400@4",
id: "runware:101@1",
defaults: {
steps: 4,
cfgScale: 3.5,