Browse Source

处理参数

feature/remove
TianYun 3 weeks ago
parent
commit
000b624229
  1. 33
      src/app/api/generate/providers/__tests__/popiserver.test.ts
  2. 3
      src/app/api/generate/providers/popiserver.ts
  3. 5
      src/app/api/generate/route.ts
  4. 16
      src/app/api/generate/task/create/__tests__/route.test.ts
  5. 3
      src/app/api/generate/task/create/route.ts
  6. 38
      src/app/api/points/calculate-task-price/__tests__/route.test.ts
  7. 15
      src/app/api/points/calculate-task-price/route.ts
  8. 2
      src/lib/popiartTask/runTask.ts
  9. 28
      src/utils/__tests__/referenceSubjectList.test.ts
  10. 17
      src/utils/referenceSubjectList.ts

33
src/app/api/generate/providers/__tests__/popiserver.test.ts

@ -95,11 +95,40 @@ describe("popiserver generation provider", () => {
aiModelCodeAlias: "viduq2-pro", aiModelCodeAlias: "viduq2-pro",
batchSize: 1, batchSize: 1,
ratio: "16:9", ratio: "16:9",
aspect_ratio: "16:9",
resolution: "720", resolution: "720",
videoRatio: 10, videoRatio: 10,
}); });
expect(body.aspectRatio).toBeUndefined(); expect(body.aspectRatio).toBeUndefined();
expect(body.aspect_ratio).toBeUndefined(); });
it("maps aspect_ratio into PopiServer task ratio fields", async () => {
vi.mocked(global.fetch).mockResolvedValue({
ok: true,
json: () => Promise.resolve({
status: "0000",
message: "ok",
data: { id: 1195 },
}),
} as Response);
const input = makeInput();
input.parameters = {
aspect_ratio: "9:16",
resolution: "720",
};
await submitPopiTask(makeRequest(), "login-token", input);
const [, init] = vi.mocked(global.fetch).mock.calls[0];
const body = JSON.parse(String(init?.body));
expect(body).toMatchObject({
ratio: "9:16",
aspect_ratio: "9:16",
width: 720,
height: 1280,
});
expect(body.aspectRatio).toBeUndefined();
}); });
it("embeds ratio and resolution in chatPrompt for gpt-image-2-vip", async () => { it("embeds ratio and resolution in chatPrompt for gpt-image-2-vip", async () => {
@ -182,10 +211,10 @@ describe("popiserver generation provider", () => {
chatPrompt: "比例:9:16\n清晰度:2K\n提示词:create a clean product render", chatPrompt: "比例:9:16\n清晰度:2K\n提示词:create a clean product render",
model: "", model: "",
ratio: "9:16", ratio: "9:16",
aspect_ratio: "9:16",
resolution: "2K", resolution: "2K",
}); });
expect(body.aspectRatio).toBeUndefined(); expect(body.aspectRatio).toBeUndefined();
expect(body.aspect_ratio).toBeUndefined();
}); });
it("builds referenceSubjectList for image tasks without @ in names", async () => { it("builds referenceSubjectList for image tasks without @ in names", async () => {

3
src/app/api/generate/providers/popiserver.ts

@ -332,7 +332,7 @@ function inferTaskSubType(input: GenerationInput, type: number): number {
} }
function resolveAspectRatio(parameters: Record<string, unknown> | undefined): string { function resolveAspectRatio(parameters: Record<string, unknown> | undefined): string {
return asString(parameters?.ratio) ?? asString(parameters?.aspectRatio) ?? "16:9"; return asString(parameters?.ratio) ?? asString(parameters?.aspectRatio) ?? asString(parameters?.aspect_ratio) ?? "16:9";
} }
function removePopiRatioAliases(parameters: Record<string, unknown>): Record<string, unknown> { function removePopiRatioAliases(parameters: Record<string, unknown>): Record<string, unknown> {
@ -544,6 +544,7 @@ function buildPopiTaskBody(input: GenerationInput, modelDetail: PopiAIModel | nu
width: asNumber(parameters.width) ?? dimensions.width, width: asNumber(parameters.width) ?? dimensions.width,
height: asNumber(parameters.height) ?? dimensions.height, height: asNumber(parameters.height) ?? dimensions.height,
ratio: aspectRatio, ratio: aspectRatio,
aspect_ratio: aspectRatio,
duration: asNumber(parameters.duration) ?? undefined, duration: asNumber(parameters.duration) ?? undefined,
resolution: asString(parameters.resolution) ?? "720p", resolution: asString(parameters.resolution) ?? "720p",
}; };

5
src/app/api/generate/route.ts

@ -25,6 +25,7 @@ import { ApiError, apiErrorResponse } from "../_errors";
import { resolveUserGatewayApiKey } from "../_gatewayApiKey"; import { resolveUserGatewayApiKey } from "../_gatewayApiKey";
import { resolveTemporaryImageUrlsInArray } from "@/lib/images/temporaryUrls"; import { resolveTemporaryImageUrlsInArray } from "@/lib/images/temporaryUrls";
import { isDataUrl, uploadDataUrlToPopiMedia } from "../_popiMedia"; import { isDataUrl, uploadDataUrlToPopiMedia } from "../_popiMedia";
import { normalizeReferenceSubjectListIds } from "@/utils/referenceSubjectList";
// Re-export for backward compatibility (test file imports from route) // Re-export for backward compatibility (test file imports from route)
export const clearFalInputMappingCache = _clearFalInputMappingCache; export const clearFalInputMappingCache = _clearFalInputMappingCache;
@ -316,7 +317,7 @@ export async function POST(request: NextRequest) {
images: stringArrayFromUnknown(images), images: stringArrayFromUnknown(images),
videos: stringArrayFromUnknown(videos), videos: stringArrayFromUnknown(videos),
voices: stringArrayFromUnknown(voices), voices: stringArrayFromUnknown(voices),
parameters, parameters: normalizeReferenceSubjectListIds(parameters),
dynamicInputs, dynamicInputs,
}; };
@ -357,7 +358,7 @@ export async function POST(request: NextRequest) {
images: stringArrayFromUnknown(images), images: stringArrayFromUnknown(images),
videos: stringArrayFromUnknown(videos), videos: stringArrayFromUnknown(videos),
voices: stringArrayFromUnknown(voices), voices: stringArrayFromUnknown(voices),
parameters, parameters: normalizeReferenceSubjectListIds(parameters),
dynamicInputs, dynamicInputs,
}; };

16
src/app/api/generate/task/create/__tests__/route.test.ts

@ -42,7 +42,13 @@ describe("/api/generate/task/create route", () => {
isSupportAudios: true, isSupportAudios: true,
}, },
}, },
parameters: { resolution: "720p" }, parameters: {
resolution: "720p",
referenceSubjectList: [
{ id: "node-image", type: "image", url: "https://cdn.example.com/ref.png", name: "图1" },
{ id: "node-video", type: "video", url: "https://cdn.example.com/ref.mp4", name: "视频2", duration: 4 },
],
},
dynamicInputs: { prompt: "make a video" }, dynamicInputs: { prompt: "make a video" },
})); }));
@ -54,7 +60,13 @@ describe("/api/generate/task/create route", () => {
videos: ["https://cdn.example.com/ref.mp4"], videos: ["https://cdn.example.com/ref.mp4"],
voices: ["https://cdn.example.com/ref.mp3"], voices: ["https://cdn.example.com/ref.mp3"],
prompt: "make a video", prompt: "make a video",
parameters: { resolution: "720p" }, parameters: {
resolution: "720p",
referenceSubjectList: [
{ id: 1, type: "image", url: "https://cdn.example.com/ref.png", name: "图1" },
{ id: 2, type: "video", url: "https://cdn.example.com/ref.mp4", name: "视频2", duration: 4 },
],
},
dynamicInputs: { prompt: "make a video" }, dynamicInputs: { prompt: "make a video" },
model: { model: {
id: "15", id: "15",

3
src/app/api/generate/task/create/route.ts

@ -4,6 +4,7 @@ import type { GenerationInput, ModelCapability } from "@/lib/providers/types";
import { submitPopiTask } from "../../providers/popiserver"; import { submitPopiTask } from "../../providers/popiserver";
import { ApiError, apiErrorResponse } from "@/app/api/_errors"; import { ApiError, apiErrorResponse } from "@/app/api/_errors";
import { requireLogin } from "@/app/api/_auth"; import { requireLogin } from "@/app/api/_auth";
import { normalizeReferenceSubjectListIds } from "@/utils/referenceSubjectList";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
export const maxDuration = 60; export const maxDuration = 60;
@ -51,7 +52,7 @@ export async function POST(request: NextRequest) {
images: body.images ? [...body.images] : [], images: body.images ? [...body.images] : [],
videos: body.videos ? [...body.videos] : [], videos: body.videos ? [...body.videos] : [],
voices: body.voices ? [...body.voices] : [], voices: body.voices ? [...body.voices] : [],
parameters: body.parameters, parameters: normalizeReferenceSubjectListIds(body.parameters),
dynamicInputs: body.dynamicInputs, dynamicInputs: body.dynamicInputs,
}; };

38
src/app/api/points/calculate-task-price/__tests__/route.test.ts

@ -83,4 +83,42 @@ describe("/api/points/calculate-task-price route", () => {
} }
); );
}); });
it("normalizes reference subject ids before forwarding task price requests", async () => {
process.env.POPIART_API_BASE_URL = "https://popi.example";
const payload = {
type: 1,
subType: 102,
aiModelId: 32,
referenceSubjectList: [
{ id: "subject-image-a", type: "image", url: "https://example.com/a.png", name: "image 1" },
{ id: "subject-video-b", type: "video", url: "https://example.com/b.mp4", name: "video 2" },
],
};
const fetchMock = vi.fn().mockResolvedValue(
new Response(JSON.stringify({ success: true, data: { taskPrice: 24 } }), {
status: 200,
headers: { "Content-Type": "application/json" },
})
);
global.fetch = fetchMock;
const response = await POST(createPostRequest(payload, {
Authorization: "Bearer user-token",
}));
expect(response.status).toBe(200);
expect(fetchMock).toHaveBeenCalledWith(
"https://popi.example/api_client/anime/task/calculateTaskPrice",
expect.objectContaining({
body: JSON.stringify({
...payload,
referenceSubjectList: [
{ id: 1, type: "image", url: "https://example.com/a.png", name: "image 1" },
{ id: 2, type: "video", url: "https://example.com/b.mp4", name: "video 2" },
],
}),
})
);
});
}); });

15
src/app/api/points/calculate-task-price/route.ts

@ -3,6 +3,7 @@ import { requireLogin } from "@/app/api/_auth";
import { ApiError, apiErrorResponse } from "@/app/api/_errors"; import { ApiError, apiErrorResponse } from "@/app/api/_errors";
import { logGatewayRequest, logGatewayResponse } from "@/app/api/_gatewayLogging"; import { logGatewayRequest, logGatewayResponse } from "@/app/api/_gatewayLogging";
import { getPopiartApiBaseUrl } from "@/app/api/_popiartBaseUrl"; import { getPopiartApiBaseUrl } from "@/app/api/_popiartBaseUrl";
import { normalizeReferenceSubjectListIds } from "@/utils/referenceSubjectList";
const UPSTREAM_CALCULATE_TASK_PRICE_PATH = "/api_client/anime/task/calculateTaskPrice"; const UPSTREAM_CALCULATE_TASK_PRICE_PATH = "/api_client/anime/task/calculateTaskPrice";
@ -22,7 +23,9 @@ export async function POST(request: NextRequest) {
throw ApiError.badRequest("aiModelId is required"); throw ApiError.badRequest("aiModelId is required");
} }
console.info("[popiserver calculateTaskPrice] request body", body); const upstreamBody = normalizeReferenceSubjectListIds(body) ?? body;
console.info("[popiserver calculateTaskPrice] request body", upstreamBody);
const url = getUpstreamUrl(request); const url = getUpstreamUrl(request);
const init: RequestInit = { const init: RequestInit = {
@ -33,19 +36,19 @@ export async function POST(request: NextRequest) {
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
token, token,
}, },
body: JSON.stringify(body), body: JSON.stringify(upstreamBody),
cache: "no-store", cache: "no-store",
}; };
const startedAt = Date.now(); const startedAt = Date.now();
logGatewayRequest("Calling task price API", url, init, { logGatewayRequest("Calling task price API", url, init, {
aiModelId: body.aiModelId, aiModelId: upstreamBody.aiModelId,
subType: body.subType, subType: upstreamBody.subType,
}); });
const response = await fetch(url, init); const response = await fetch(url, init);
await logGatewayResponse("Task price API response", url, init, response, { await logGatewayResponse("Task price API response", url, init, response, {
aiModelId: body.aiModelId, aiModelId: upstreamBody.aiModelId,
subType: body.subType, subType: upstreamBody.subType,
}, startedAt); }, startedAt);
const payload = await response.json().catch(() => null); const payload = await response.json().catch(() => null);
console.info("[popiserver calculateTaskPrice] response", { console.info("[popiserver calculateTaskPrice] response", {

2
src/lib/popiartTask/runTask.ts

@ -1,6 +1,7 @@
"use client"; "use client";
import type { GenerateResponse, SelectedModel } from "@/types"; import type { GenerateResponse, SelectedModel } from "@/types";
import { normalizeReferenceSubjectListIds } from "@/utils/referenceSubjectList";
export type PopiartTaskPayload = { export type PopiartTaskPayload = {
images: string[]; images: string[];
@ -43,6 +44,7 @@ function omitSelectedModelProvider(selectedModel: SelectedModel): Omit<SelectedM
function serializeTaskCreatePayload(payload: PopiartTaskPayload): string { function serializeTaskCreatePayload(payload: PopiartTaskPayload): string {
return JSON.stringify({ return JSON.stringify({
...payload, ...payload,
parameters: normalizeReferenceSubjectListIds(payload.parameters),
selectedModel: omitSelectedModelProvider(payload.selectedModel), selectedModel: omitSelectedModelProvider(payload.selectedModel),
}); });
} }

28
src/utils/__tests__/referenceSubjectList.test.ts

@ -0,0 +1,28 @@
import { describe, expect, it } from "vitest";
import { normalizeReferenceSubjectListIds } from "../referenceSubjectList";
describe("normalizeReferenceSubjectListIds", () => {
it("maps reference subject ids to numeric sequence values", () => {
const parameters = normalizeReferenceSubjectListIds({
referenceSubjectList: [
{ id: "node-a", type: "image", url: "https://example.com/a.png", name: "图1" },
{ id: "node-b", type: "video", url: "https://example.com/b.mp4", name: "视频2", duration: 3 },
],
resolution: "720p",
});
expect(parameters).toEqual({
referenceSubjectList: [
{ id: 1, type: "image", url: "https://example.com/a.png", name: "图1" },
{ id: 2, type: "video", url: "https://example.com/b.mp4", name: "视频2", duration: 3 },
],
resolution: "720p",
});
});
it("leaves parameters without reference subjects unchanged", () => {
const parameters = { resolution: "720p" };
expect(normalizeReferenceSubjectListIds(parameters)).toBe(parameters);
});
});

17
src/utils/referenceSubjectList.ts

@ -0,0 +1,17 @@
export function normalizeReferenceSubjectListIds(
parameters: Record<string, unknown> | undefined
): Record<string, unknown> | undefined {
const referenceSubjectList = parameters?.referenceSubjectList;
if (!Array.isArray(referenceSubjectList)) return parameters;
return {
...parameters,
referenceSubjectList: referenceSubjectList.map((item, index) => {
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
return {
...item,
id: index + 1,
};
}),
};
}
Loading…
Cancel
Save