Skip to content

Commit

Permalink
*: v2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
montyanderson committed Oct 3, 2024
1 parent d5120c6 commit 3985f4e
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 12 deletions.
24 changes: 13 additions & 11 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
name: Validate Formatting & Types
name: Formatting, Types, & Test

on: [push, pull_request]

jobs:
validate:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
validate:
runs-on: ubuntu-latest

- uses: denoland/setup-deno@v1
with:
deno-version: v1.x
steps:
- uses: actions/checkout@v3

- run: deno fmt --check
- uses: denoland/setup-deno@v1
with:
deno-version: v1.x

- run: deno check prodia.ts
- run: deno fmt --check

- run: deno check prodia.ts

- run: deno test --allow-env --allow-net
29 changes: 29 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 34 additions & 0 deletions test/v2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { assertEquals } from "jsr:@std/assert";
import { createProdia } from "../v2/index.ts";

const token = Deno.env.get("PRODIA_TOKEN");

if (typeof token !== "string") {
throw new Error("PRODIA_TOKEN is not set");
}

const isJpeg = (image: ArrayBuffer): boolean => {
const view = new Uint8Array(image);

return view[0] === 0xFF && view[1] === 0xD8;
};

await Deno.test("Example Job: JPEG Output", async () => {
const client = createProdia({
token,
});

const job = await client.job({
"type": "inference.flux.dev.txt2img.v1",
"config": {
"prompt": "puppies in a cloud, 4k",
"steps": 1,
"width": 1024,
"height": 1024,
},
});

const image = await job.arrayBuffer();

assertEquals(isJpeg(image), true, "Image should be a JPEG");
});
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"baseUrl": ".",
"declaration": true
},
"files": ["prodia.ts"]
"files": ["prodia.ts", "v2/index.ts"]
}
117 changes: 117 additions & 0 deletions v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
type JsonObject =
& { [Key in string]: JsonValue }
& { [Key in string]?: JsonValue | undefined };
type JsonArray = JsonValue[] | readonly JsonValue[];
type JsonPrimitive = string | number | boolean | null;
type JsonValue = JsonPrimitive | JsonObject | JsonArray;

/* job and job configuration */

export type ProdiaJob = Record<string, JsonValue>;

export type ProdiaJobOptions = {
accept:
| "image/png"
| "image/jpeg"
| "image/webp"
| "multipart/form-data"
| "video/mp4";
};

const defaultJobOptions: ProdiaJobOptions = {
accept: "image/jpeg",
};

export type ProdiaJobResponse = {
arrayBuffer: () => Promise<ArrayBuffer>; // we only support direct image response now
};

/* client & client configuration*/

export type Prodia = {
job: (
params: ProdiaJob,
options?: Partial<ProdiaJobOptions>,
) => Promise<ProdiaJobResponse>;
};

export type CreateProdiaOptions = {
token: string;
baseUrl?: string;
maxErrors?: number;
maxRetries?: number;
};

/* error types */

export class ProdiaCapacityError extends Error {}
export class ProdiaBadResponseError extends Error {}

export const createProdia = ({
token,
baseUrl = "https://inference.prodia.com/v2",
maxErrors = 1,
maxRetries = Infinity,
}: CreateProdiaOptions): Prodia => {
const job = async (
params: ProdiaJob,
_options?: Partial<ProdiaJobOptions>,
) => {
const options = {
...defaultJobOptions,
..._options,
};

let response: Response;

let errors = 0;
let retries = 0;

do {
response = await fetch(`${baseUrl}/job`, {
method: "POST",
headers: {
"Authorization": `Bearer ${token}`,
"Accept": options.accept,
"Content-Type": "application/json",
},
body: JSON.stringify(params),
});

if (response.status === 429) {
retries += 1;
} else if (response.status < 200 || response.status > 299) {
errors += 1;
}

const retryAfter = Number(response.headers.get("Retry-After")) || 1;
await new Promise((resolve) =>
setTimeout(resolve, retryAfter * 1000)
);
} while (
(response.status < 200 || response.status > 299) &&
errors <= maxErrors &&
retries <= maxRetries
);

if (response.status === 429) {
throw new ProdiaCapacityError(
"ProdiaCapacityError: Unable to schedule job with current token",
);
}

if (response.status < 200 || response.status > 299) {
throw new ProdiaBadResponseError(
`ProdiaBadResponseError: ${response.status} ${response.statusText}`,
);
}

return {
arrayBuffer: () => response.arrayBuffer(),
};
};

return {
job,
};
};

0 comments on commit 3985f4e

Please sign in to comment.