Merge pull request '保留框选工具栏与代理命令的既有计费规则' (#48) from codex/box-cutout-billing-paths-20261105 into main
This commit is contained in:
commit
aea18a7fc6
@ -1,3 +1,4 @@
|
||||
import { executeBoxCutoutJob } from "@/lib/ai/box-cutout/service";
|
||||
import { executeExtendJob } from "@/lib/ai/extend/service";
|
||||
import { executeLayerSplitJob } from "@/lib/ai/layer-split/service";
|
||||
import { executeResizeJob } from "@/lib/ai/resize/service";
|
||||
@ -186,25 +187,31 @@ async function executeForTarget(
|
||||
}
|
||||
|
||||
if (command.capabilityId === "image.box_cutout") {
|
||||
const response = await executeBoxCutoutPOST(new Request(params.requestUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Request-ID": command.requestId },
|
||||
body: JSON.stringify({
|
||||
imageUrl: target.sourceImageUrl,
|
||||
sourceNodeId: target.nodeId,
|
||||
cropRect: {
|
||||
x: asFiniteNumber(command.args.x),
|
||||
y: asFiniteNumber(command.args.y),
|
||||
width: asFiniteNumber(command.args.width),
|
||||
height: asFiniteNumber(command.args.height),
|
||||
},
|
||||
paddingRatio: asFiniteNumber(command.args.paddingRatio),
|
||||
prompt: prompt || "框选抠图",
|
||||
}),
|
||||
}), params.userId);
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
throw new RmbgServiceError(result.detail || result.message || result.error || "框选抠图未能完成", response.status);
|
||||
const job = {
|
||||
imageUrl: target.sourceImageUrl,
|
||||
sourceNodeId: target.nodeId,
|
||||
cropRect: {
|
||||
x: asFiniteNumber(command.args.x),
|
||||
y: asFiniteNumber(command.args.y),
|
||||
width: asFiniteNumber(command.args.width),
|
||||
height: asFiniteNumber(command.args.height),
|
||||
},
|
||||
paddingRatio: asFiniteNumber(command.args.paddingRatio),
|
||||
prompt: prompt || "框选抠图",
|
||||
};
|
||||
let result;
|
||||
if (command.source === "toolbar") {
|
||||
const response = await executeBoxCutoutPOST(new Request(params.requestUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", "X-Request-ID": command.requestId },
|
||||
body: JSON.stringify(job),
|
||||
}), params.userId);
|
||||
result = await response.json();
|
||||
if (!response.ok || !result.success) {
|
||||
throw new RmbgServiceError(result.detail || result.message || result.error || "框选抠图未能完成", response.status);
|
||||
}
|
||||
} else {
|
||||
result = await executeBoxCutoutJob({ ...job, requestId: command.requestId, userId: params.userId });
|
||||
}
|
||||
return [{
|
||||
...common,
|
||||
|
||||
@ -102,7 +102,7 @@ test("mixed-model batches bill and label only the delivered images using their a
|
||||
test("headless image features cannot substitute their default when a pinned model disappears", async () => {
|
||||
let generated = 0;
|
||||
const mocks: Record<string, any> = {
|
||||
"@/lib/canvas/operations/box-cutout": {}, "@/lib/ai/extend/service": {}, "@/lib/ai/layer-split/service": {}, "@/lib/ai/resize/service": {}, "@/lib/ai/rmbg/service": {},
|
||||
"@/lib/canvas/operations/box-cutout": {}, "@/lib/ai/box-cutout/service": {}, "@/lib/ai/extend/service": {}, "@/lib/ai/layer-split/service": {}, "@/lib/ai/resize/service": {}, "@/lib/ai/rmbg/service": {},
|
||||
"@/lib/canvas/capabilities/internal-endpoint": {}, "@/lib/client/long-task": {},
|
||||
"@/lib/canvas/generate-image": { generateCanvasImage: async () => { generated++; return { images: [] }; } },
|
||||
"@/lib/style-pro/model-options": { getStyleProImageModelEntries: async () => [{ model: { id: "default", modelId: "default", isDefault: true } }], sortStyleProImageModelEntries: (entries: any[]) => entries, findStyleProImageModelByRef: () => null },
|
||||
|
||||
@ -68,6 +68,7 @@ test("headless box cutout keeps its task identity, pixel bounds and input-reject
|
||||
let rejectInput = false;
|
||||
const bounds = { left: 20, top: 30, width: 200, height: 300 };
|
||||
const executor = serverLoader({
|
||||
"@/lib/ai/box-cutout/service": {},
|
||||
"@/lib/canvas/operations/box-cutout": { executeBoxCutoutPOST: async (request: Request, userId: string) => {
|
||||
calls++;
|
||||
expect(userId).toBe("user-a");
|
||||
@ -81,7 +82,7 @@ test("headless box cutout keeps its task identity, pixel bounds and input-reject
|
||||
"@/lib/canvas/capabilities/internal-endpoint": {}, "@/lib/client/long-task": {},
|
||||
"@/lib/canvas/generate-image": {}, "@/lib/style-pro/model-options": {},
|
||||
})("lib/canvas/capabilities/headless-executor.ts");
|
||||
const params = { userId: "user-a", requestUrl: "https://example.com/api/canvas/project-a/capabilities", requestHeaders: new Headers(), command: { requestId: "box-a", capabilityId: "image.box_cutout", args: { x: 0.1, y: 0.2, width: 0.3, height: 0.4, paddingRatio: 0.08 } }, targets: [{ nodeId: "source", title: "原图", sourceImageUrl: `${cdn}/original.png` }] };
|
||||
const params = { userId: "user-a", requestUrl: "https://example.com/api/canvas/project-a/capabilities", requestHeaders: new Headers(), command: { requestId: "box-a", capabilityId: "image.box_cutout", source: "toolbar", args: { x: 0.1, y: 0.2, width: 0.3, height: 0.4, paddingRatio: 0.08 } }, targets: [{ nodeId: "source", title: "原图", sourceImageUrl: `${cdn}/original.png` }] };
|
||||
const results = await executor.executeCanvasCapabilityHeadlessly(params);
|
||||
expect(calls).toBe(1);
|
||||
expect(results).toMatchObject([{ sourceNodeId: "source", url: `${cdn}/cutout.png`, metadata: { editMode: "box-cutout", paddedCropRectPixels: bounds } }]);
|
||||
@ -90,6 +91,25 @@ test("headless box cutout keeps its task identity, pixel bounds and input-reject
|
||||
expect(calls).toBe(2);
|
||||
});
|
||||
|
||||
test("Agent and MCP box cutouts retain their existing billing path", async () => {
|
||||
let jobs = 0, featureCharges = 0;
|
||||
const executor = serverLoader({
|
||||
"@/lib/ai/box-cutout/service": { executeBoxCutoutJob: async (params: any) => {
|
||||
jobs++;
|
||||
expect(params).toMatchObject({ userId: "user-a", requestId: "box-agent", sourceNodeId: "source" });
|
||||
return { original_url: `${cdn}/cutout.png` };
|
||||
} },
|
||||
"@/lib/canvas/operations/box-cutout": { executeBoxCutoutPOST: async () => { featureCharges++; throw new Error("Unexpected toolbar charge"); } },
|
||||
"@/lib/ai/extend/service": {}, "@/lib/ai/layer-split/service": {}, "@/lib/ai/resize/service": {}, "@/lib/ai/rmbg/service": {},
|
||||
"@/lib/canvas/capabilities/internal-endpoint": {}, "@/lib/client/long-task": {},
|
||||
"@/lib/canvas/generate-image": {}, "@/lib/style-pro/model-options": {},
|
||||
})("lib/canvas/capabilities/headless-executor.ts");
|
||||
for (const source of ["agent", "mcp", "system"]) {
|
||||
await executor.executeCanvasCapabilityHeadlessly({ userId: "user-a", command: { requestId: "box-agent", capabilityId: "image.box_cutout", source, args: { x: 0, y: 0, width: 0.5, height: 0.5 } }, targets: [{ nodeId: "source", title: "原图", sourceImageUrl: `${cdn}/source.png` }] });
|
||||
}
|
||||
expect({ jobs, featureCharges }).toEqual({ jobs: 3, featureCharges: 0 });
|
||||
});
|
||||
|
||||
function rmbgLoader(fetch: typeof globalThis.fetch, timer?: typeof setTimeout) {
|
||||
return serverLoader({
|
||||
"@/lib/db/queries": { getSystemSetting: async () => ({ value: { provider: "aoscdn", aosApiKey: "test-key" } }) },
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user