diff --git a/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java b/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java index d964c9f..27e2b6d 100644 --- a/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java +++ b/api/src/main/java/com/theokanning/openai/finetune/FineTuneResult.java @@ -6,7 +6,7 @@ import lombok.Data; import java.util.List; /** - * An object describing an fine-tuned model. Returned by multiple fine-tune requests. + * An object describing a fine-tuned model. Returned by multiple fine-tune requests. * * https://beta.openai.com/docs/api-reference/fine-tunes */ diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java new file mode 100644 index 0000000..ec4f55a --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageEditRequest.java @@ -0,0 +1,42 @@ +package com.theokanning.openai.image; + +import lombok.*; + +/** + * A request for OpenAi to edit an image based on a prompt + * All fields except prompt are optional + * + * https://beta.openai.com/docs/api-reference/images/create-edit + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class CreateImageEditRequest { + + /** + * A text description of the desired image(s). The maximum length in 1000 characters. + */ + @NonNull + String prompt; + + /** + * The number of images to generate. Must be between 1 and 10. Defaults to 1. + */ + Integer n; + + /** + * The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024". Defaults to "1024x1024". + */ + String size; + + /** + * The format in which the generated images are returned. Must be one of url or b64_json. Defaults to url. + */ + String responseFormat; + + /** + * A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. + */ + String user; +} diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java new file mode 100644 index 0000000..8e94d82 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageRequest.java @@ -0,0 +1,42 @@ +package com.theokanning.openai.image; + +import lombok.*; + +/** + * A request for OpenAi to create an image based on a prompt + * All fields except prompt are optional + * + * https://beta.openai.com/docs/api-reference/images/create + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class CreateImageRequest { + + /** + * A text description of the desired image(s). The maximum length in 1000 characters. + */ + @NonNull + String prompt; + + /** + * The number of images to generate. Must be between 1 and 10. Defaults to 1. + */ + Integer n; + + /** + * The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024". Defaults to "1024x1024". + */ + String size; + + /** + * The format in which the generated images are returned. Must be one of url or b64_json. Defaults to url. + */ + String responseFormat; + + /** + * A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. + */ + String user; +} diff --git a/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java b/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java new file mode 100644 index 0000000..6798dba --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/image/CreateImageVariationRequest.java @@ -0,0 +1,36 @@ +package com.theokanning.openai.image; + +import lombok.*; + +/** + * A request for OpenAi to create a variation of an image + * All fields are optional + * + * https://beta.openai.com/docs/api-reference/images/create-variation + */ +@Builder +@NoArgsConstructor +@AllArgsConstructor +@Data +public class CreateImageVariationRequest { + + /** + * The number of images to generate. Must be between 1 and 10. Defaults to 1. + */ + Integer n; + + /** + * The size of the generated images. Must be one of "256x256", "512x512", or "1024x1024". Defaults to "1024x1024". + */ + String size; + + /** + * The format in which the generated images are returned. Must be one of url or b64_json. Defaults to url. + */ + String responseFormat; + + /** + * A unique identifier representing your end-user, which will help OpenAI to monitor and detect abuse. + */ + String user; +} diff --git a/api/src/main/java/com/theokanning/openai/image/Image.java b/api/src/main/java/com/theokanning/openai/image/Image.java new file mode 100644 index 0000000..e321484 --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/image/Image.java @@ -0,0 +1,24 @@ +package com.theokanning.openai.image; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * An object containing either a URL or a base 64 encoded image. + * + * https://beta.openai.com/docs/api-reference/images + */ +@Data +public class Image { + /** + * The URL where the image can be accessed. + */ + String url; + + + /** + * Base64 encoded image string. + */ + @JsonProperty("b64_json") + String b64Json; +} diff --git a/api/src/main/java/com/theokanning/openai/image/ImageResult.java b/api/src/main/java/com/theokanning/openai/image/ImageResult.java new file mode 100644 index 0000000..dc0b5ab --- /dev/null +++ b/api/src/main/java/com/theokanning/openai/image/ImageResult.java @@ -0,0 +1,24 @@ +package com.theokanning.openai.image; + +import lombok.Data; + +import java.util.List; + +/** + * An object with a list of image results. + * + * https://beta.openai.com/docs/api-reference/images + */ +@Data +public class ImageResult { + + /** + * The creation time in epoch seconds. + */ + Long createdAt; + + /** + * List of image results. + */ + List data; +} diff --git a/client/src/main/java/com/theokanning/openai/OpenAiApi.java b/client/src/main/java/com/theokanning/openai/OpenAiApi.java index 5fa1c36..be6a648 100644 --- a/client/src/main/java/com/theokanning/openai/OpenAiApi.java +++ b/client/src/main/java/com/theokanning/openai/OpenAiApi.java @@ -11,6 +11,9 @@ import com.theokanning.openai.file.File; import com.theokanning.openai.finetune.FineTuneEvent; import com.theokanning.openai.finetune.FineTuneRequest; import com.theokanning.openai.finetune.FineTuneResult; +import com.theokanning.openai.image.CreateImageEditRequest; +import com.theokanning.openai.image.CreateImageRequest; +import com.theokanning.openai.image.ImageResult; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; @@ -82,6 +85,15 @@ public interface OpenAiApi { @DELETE("/v1/models/{fine_tune_id}") Single deleteFineTune(@Path("fine_tune_id") String fineTuneId); + @POST("/v1/images/generations") + Single createImage(@Body CreateImageRequest request); + + @POST("/v1/images/edits") + Single createImageEdit(@Body RequestBody requestBody); + + @POST("/v1/images/variations") + Single createImageVariation(@Body RequestBody requestBody); + @POST("/v1/moderations") Single createModeration(@Body ModerationRequest request); diff --git a/client/src/main/java/com/theokanning/openai/OpenAiService.java b/client/src/main/java/com/theokanning/openai/OpenAiService.java index ffae99b..444f7e2 100644 --- a/client/src/main/java/com/theokanning/openai/OpenAiService.java +++ b/client/src/main/java/com/theokanning/openai/OpenAiService.java @@ -15,6 +15,7 @@ import com.theokanning.openai.file.File; import com.theokanning.openai.finetune.FineTuneEvent; import com.theokanning.openai.finetune.FineTuneRequest; import com.theokanning.openai.finetune.FineTuneResult; +import com.theokanning.openai.image.*; import com.theokanning.openai.model.Model; import com.theokanning.openai.moderation.ModerationRequest; import com.theokanning.openai.moderation.ModerationResult; @@ -169,6 +170,62 @@ public class OpenAiService { return api.deleteFineTune(fineTuneId).blockingGet(); } + public ImageResult createImage(CreateImageRequest request) { + return api.createImage(request).blockingGet(); + } + + public ImageResult createImageEdit(CreateImageEditRequest request, String imagePath, String maskPath) { + java.io.File image = new java.io.File(imagePath); + java.io.File mask = null; + if (maskPath != null) { + mask = new java.io.File(maskPath); + } + return createImageEdit(request, image, mask); + } + + public ImageResult createImageEdit(CreateImageEditRequest request, java.io.File image, java.io.File mask) { + RequestBody imageBody = RequestBody.create(MediaType.parse("image"), image); + + MultipartBody.Builder builder = new MultipartBody.Builder() + .setType(MediaType.get("multipart/form-data")) + .addFormDataPart("prompt", request.getPrompt()) + .addFormDataPart("size", request.getSize()) + .addFormDataPart("response_format", request.getResponseFormat()) + .addFormDataPart("image", "image", imageBody); + + if (request.getN() != null) { + builder.addFormDataPart("n", request.getN().toString()); + } + + if (mask != null) { + RequestBody maskBody = RequestBody.create(MediaType.parse("image"), mask); + builder.addFormDataPart("mask", "mask", maskBody); + } + + return api.createImageEdit(builder.build()).blockingGet(); + } + + public ImageResult createImageVariation(CreateImageVariationRequest request, String imagePath) { + java.io.File image = new java.io.File(imagePath); + return createImageVariation(request, image); + } + + public ImageResult createImageVariation(CreateImageVariationRequest request, java.io.File image) { + RequestBody imageBody = RequestBody.create(MediaType.parse("image"), image); + + MultipartBody.Builder builder = new MultipartBody.Builder() + .setType(MediaType.get("multipart/form-data")) + .addFormDataPart("size", request.getSize()) + .addFormDataPart("response_format", request.getResponseFormat()) + .addFormDataPart("image", "image", imageBody); + + if (request.getN() != null) { + builder.addFormDataPart("n", request.getN().toString()); + } + + return api.createImageVariation(builder.build()).blockingGet(); + } + public ModerationResult createModeration(ModerationRequest request) { return api.createModeration(request).blockingGet(); } diff --git a/client/src/test/java/com/theokanning/openai/ImageTest.java b/client/src/test/java/com/theokanning/openai/ImageTest.java new file mode 100644 index 0000000..be29073 --- /dev/null +++ b/client/src/test/java/com/theokanning/openai/ImageTest.java @@ -0,0 +1,95 @@ +package com.theokanning.openai; + +import com.theokanning.openai.image.CreateImageEditRequest; +import com.theokanning.openai.image.CreateImageRequest; +import com.theokanning.openai.image.CreateImageVariationRequest; +import com.theokanning.openai.image.Image; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + + +public class ImageTest { + + static String filePath = "src/test/resources/penguin.png"; + static String fileWithAlphaPath = "src/test/resources/penguin_with_alpha.png"; + static String maskPath = "src/test/resources/mask.png"; + + String token = System.getenv("OPENAI_TOKEN"); + OpenAiService service = new OpenAiService(token, 30); + + + @Test + void createImageUrl() { + CreateImageRequest createImageRequest = CreateImageRequest.builder() + .prompt("penguin") + .n(3) + .size("256x256") + .user("testing") + .build(); + + List images = service.createImage(createImageRequest).getData(); + assertEquals(3, images.size()); + assertNotNull(images.get(0).getUrl()); + } + + @Test + void createImageBase64() { + CreateImageRequest createImageRequest = CreateImageRequest.builder() + .prompt("penguin") + .responseFormat("b64_json") + .user("testing") + .build(); + + List images = service.createImage(createImageRequest).getData(); + assertEquals(1, images.size()); + assertNotNull(images.get(0).getB64Json()); + } + + @Test + void createImageEdit() { + CreateImageEditRequest createImageRequest = CreateImageEditRequest.builder() + .prompt("a penguin with a red background") + .responseFormat("url") + .size("256x256") + .user("testing") + .n(2) + .build(); + + List images = service.createImageEdit(createImageRequest, fileWithAlphaPath, null).getData(); + assertEquals(2, images.size()); + assertNotNull(images.get(0).getUrl()); + } + + @Test + void createImageEditWithMask() { + CreateImageEditRequest createImageRequest = CreateImageEditRequest.builder() + .prompt("a penguin with a red hat") + .responseFormat("url") + .size("256x256") + .user("testing") + .n(2) + .build(); + + List images = service.createImageEdit(createImageRequest, filePath, maskPath).getData(); + assertEquals(2, images.size()); + assertNotNull(images.get(0).getUrl()); + } + + @Test + void createImageVariation() { + CreateImageVariationRequest createImageVariationRequest = CreateImageVariationRequest.builder() + .responseFormat("url") + .size("256x256") + .user("testing") + .n(2) + .build(); + + List images = service.createImageVariation(createImageVariationRequest, filePath).getData(); + assertEquals(2, images.size()); + assertNotNull(images.get(0).getUrl()); + } +} diff --git a/client/src/test/resources/mask.png b/client/src/test/resources/mask.png new file mode 100644 index 0000000..84fcfb3 Binary files /dev/null and b/client/src/test/resources/mask.png differ diff --git a/client/src/test/resources/penguin.png b/client/src/test/resources/penguin.png new file mode 100644 index 0000000..91b023c Binary files /dev/null and b/client/src/test/resources/penguin.png differ diff --git a/client/src/test/resources/penguin_with_alpha.png b/client/src/test/resources/penguin_with_alpha.png new file mode 100644 index 0000000..4636c4f Binary files /dev/null and b/client/src/test/resources/penguin_with_alpha.png differ