Skip to content

Commit

Permalink
*: v2 support (#13)
Browse files Browse the repository at this point in the history
This PR contains:

* Everything you need to do a v2 txt2img generation
* Automatic testing with Actions
* Updated `README.md`
  • Loading branch information
montyanderson authored Oct 3, 2024
1 parent d5120c6 commit 0edf90c
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 14 deletions.
26 changes: 15 additions & 11 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
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
env:
PRODIA_TOKEN: ${{ secrets.PRODIA_TOKEN }}
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.

36 changes: 34 additions & 2 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

Official TypeScript library for Prodia's AI inference API.

- [Get an API Key](https://app.prodia.com/api)

- [Get an v1 API Key or v2 Token](https://app.prodia.com/api)
- [v2 API Explorer](https://app.prodia.com/explorer)
- [View Docs + Pricing](https://docs.prodia.com/reference/getting-started)

## Usage
Expand All @@ -15,6 +15,34 @@ Official TypeScript library for Prodia's AI inference API.
npm install prodia --save
```

## v2

As of _October 2024_, we require users to have a **Pro+** or **Enterprise** subscription with us to use our v2 API. This is to ensure quality of service. However, we expect to revisit this by EOY and make it available more broadly.

```javascript
import { createProdia } from "prodia/v2"; // v2 :)

const prodia = createProdia({
token: "...", // grab a token from https://app.prodia.com/api
});

(async () => {
// run a flux dev generation
const job = await client.job({
"type": "inference.flux.dev.txt2img.v1",
"config": {
"prompt": "puppies in a cloud, 4k",
"steps": 25,
},
});

const image = await job.arrayBuffer();
// display your image
})();
```

## v1 Legacy API

```javascript
import { createProdia } from "prodia";

Expand All @@ -32,3 +60,7 @@ const prodia = createProdia({
// check status and view your image :)
})();
```

## help

Email us at [[email protected]](mailto:[email protected]).
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 0edf90c

Please sign in to comment.