Compare commits

..

No commits in common. "main" and "codex/native-image-expansion-20261112" have entirely different histories.

2 changed files with 7 additions and 56 deletions

View File

@ -12,8 +12,6 @@ import { expandWithVip98kOutpaint, VIP98K_OUTPAINT_MODEL } from "@/lib/ai/extend
import { db } from "@/lib/db";
import { generatedImage } from "@/lib/db/schema";
import { uploadFile } from "@/lib/storage/service";
import { archiveTemporaryImage } from "@/lib/storage/image-archive";
import { randomUUID } from "node:crypto";
type ExtendConfig = {
provider?: ExtendProvider;
@ -436,10 +434,7 @@ export async function executeExtendJob(
}
const startTime = Date.now();
let buffer!: Buffer;
let uploadResult: { thumbnail: string; original: string } | undefined;
let archivedBytes: number | undefined;
let downloadAsset: Awaited<ReturnType<typeof archiveTemporaryImage>>["downloadAsset"];
let buffer: Buffer;
let modelId: string;
if (config.provider === "fal") {
buffer = await expandWithFalOutpaint({ imageUrl: inputImageUrl, apiKey: config.apiKey!, xScale, yScale });
@ -455,28 +450,21 @@ export async function executeExtendJob(
} else {
const prepared = config.provider === "vip98k" ? inputImageUrl : await normalizeDashscopeExpansionInput(inputImageUrl);
const url = await extendImage(prepared, config, xScale, yScale, requestId);
if (config.provider === "vip98k" && !includeBase64) {
const archived = await archiveTemporaryImage(url, requestId || randomUUID());
uploadResult = { original: archived.url, thumbnail: archived.thumbnailUrl };
archivedBytes = archived.bytes;
downloadAsset = archived.downloadAsset;
} else {
const imageResponse = await fetch(url);
if (!imageResponse.ok) {
throw new ExtendServiceError("下载拓展图片失败", 500, `${imageResponse.status} ${imageResponse.statusText}`);
}
buffer = Buffer.from(await imageResponse.arrayBuffer());
const imageResponse = await fetch(url);
if (!imageResponse.ok) {
throw new ExtendServiceError("下载拓展图片失败", 500, `${imageResponse.status} ${imageResponse.statusText}`);
}
buffer = Buffer.from(await imageResponse.arrayBuffer());
modelId = config.provider === "vip98k" ? "image-expansion" : "extend-1.0";
}
const imageBase64Result = includeBase64 ? buffer.toString("base64") : undefined;
uploadResult ??= await uploadBufferPairToS3(buffer, config.provider === "fal" || config.provider === "vip98k" ? "png" : "jpg");
const uploadResult = await uploadBufferPairToS3(buffer, config.provider === "fal" || config.provider === "vip98k" ? "png" : "jpg");
console.log("[Extend Service] Result uploaded:", {
requestId,
includeBase64,
inputImageUrl: inputImageUrl.slice(0, 120),
modelId,
outputBytes: archivedBytes ?? buffer.length,
outputBytes: buffer.length,
});
let mediaAssetId: string | null = null;
let generatedImageId: string | null = null;
@ -525,7 +513,6 @@ export async function executeExtendJob(
xScale,
yScale,
imageSize: "2K",
...(downloadAsset ? { downloadAsset } : {}),
},
});
mediaAssetId = media?.id || null;

View File

@ -13,7 +13,6 @@ test("native expansion sends the original image and never switches models after
"@/lib/db": {},
"@/lib/db/schema": {},
"@/lib/storage/service": {},
"@/lib/storage/image-archive": {},
"@/lib/ai/vip98k-runtime": {
executeConfiguredVip98kTask: async (params: unknown) => { calls.push(params); throw new Error(message); },
},
@ -23,41 +22,6 @@ test("native expansion sends the original image and never switches models after
}
});
test("native expansion archives the completed image without transferring its bytes through the app", async () => {
const calls: string[] = [];
const service = serverLoader({
"@/lib/db/queries": { getSystemSetting: async () => ({ value: { provider: "vip98k", modelId: "image-expansion" } }) },
"@/lib/db": {},
"@/lib/db/schema": {},
"@/lib/storage/service": {},
"@/lib/ai/vip98k-runtime": { executeConfiguredVip98kTask: async () => ({ outputUrls: ["https://relay.test/result.png"] }) },
"@/lib/storage/image-archive": { archiveTemporaryImage: async (url: string, id: string) => {
calls.push(url, id);
return { url: "https://cdn.test/native.png", thumbnailUrl: "https://cdn.test/native.png", bytes: 12375212 };
} },
})("lib/ai/extend/service.ts");
const result = await service.executeExtendJob({ userId: "test", imageUrl: "https://source.test/original.jpg", requestId: "request-1", skipGallerySave: true });
expect(calls).toEqual(["https://relay.test/result.png", "request-1"]);
expect(result.original_url).toBe("https://cdn.test/native.png");
expect(result.image_base64).toBeUndefined();
});
test("native expansion still returns image bytes to callers requesting base64", async () => {
const image = await sharp({ create: { width: 12, height: 8, channels: 3, background: "red" } }).png().toBuffer();
const service = serverLoader({
"@/lib/db/queries": { getSystemSetting: async () => ({ value: { provider: "vip98k", modelId: "image-expansion" } }) },
"@/lib/db": {},
"@/lib/db/schema": {},
"@/lib/storage/service": { uploadFile: async (name: string) => ({ url: `https://cdn.test/${name}` }) },
"@/lib/storage/image-archive": { archiveTemporaryImage: () => { throw new Error("Base64 callers need the image bytes"); } },
"@/lib/ai/vip98k-runtime": { executeConfiguredVip98kTask: async () => ({ outputUrls: ["https://relay.test/result.png"] }) },
fetch: async () => new Response(new Uint8Array(image)),
})("lib/ai/extend/service.ts");
const result = await service.executeExtendJob({ userId: "test", imageUrl: "https://source.test/original.jpg", includeBase64: true, skipGallerySave: true });
expect(result.image_base64).toBe(image.toString("base64"));
expect(result.original_url).toMatch(/\.png$/);
});
test.describe("image extension provider configuration", () => {
test("uses the most recently updated Alibaba image provider key", () => {
expect(