fix(realtime-canvas): use runware seedImage-compatible model
This commit is contained in:
parent
6c30ab90ef
commit
80e826039f
@ -34,7 +34,7 @@ const defaultConfig = {
|
||||
jpegQuality: 0.55,
|
||||
},
|
||||
model: {
|
||||
id: "runware:400@4",
|
||||
id: "runware:101@1",
|
||||
defaults: {
|
||||
steps: 4,
|
||||
cfgScale: 3.5,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user