Skip to content

Commit 54e4942

Browse files
authored
Support images api (#15)
2 parents dd827de + c82c347 commit 54e4942

File tree

16 files changed

+472
-11
lines changed

16 files changed

+472
-11
lines changed

.github/workflows/checker.yml

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,14 +71,7 @@ jobs:
7171
distribution: 'temurin'
7272
- run: chmod 755 ./mvnw
7373
- run: |
74-
./mvnw clean install test \
75-
-Dfindbugs.skip \
76-
-Dcheckstyle.skip \
77-
-Dgpg.skip -Dskip.yarn \
78-
-Dopenai.token=${{ secrets.OPENAI_TOKEN }} \
79-
-Dproxy.token=${{ secrets.PROXY_TOKEN }} \
80-
-Dproxy.host=${{ secrets.PROXY_HOST }} \
81-
-Dazure.token=${{ secrets.AZURE_TOKEN}}
74+
./mvnw clean install test -Dfindbugs.skip -Dcheckstyle.skip -Dgpg.skip -Dskip.yarn -Dopenai.token=${{ secrets.OPENAI_TOKEN }} -Dproxy.token=${{ secrets.PROXY_TOKEN }} -Dproxy.host=${{ secrets.PROXY_HOST }} -Dazure.token=${{ secrets.AZURE_TOKEN}}
8275
8376
before_checker_package:
8477
runs-on: ubuntu-latest

.github/workflows/publish-maven.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ on:
66

77
jobs:
88
release:
9-
runs-on: ubuntu-18.04
9+
runs-on: ubuntu-latest
1010
steps:
1111
- name: Check out Git repository
1212
uses: actions/checkout@v2

docs/docs/released.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ hide:
55
- navigation
66
---
77

8+
### 1.3.0
9+
10+
---
11+
12+
- Support azure proxy
13+
- Supports automatic resource release
14+
815
### 1.2.0
916

1017
---

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>org.devlive.sdk</groupId>
77
<artifactId>openai-java-sdk</artifactId>
8-
<version>1.3.0</version>
8+
<version>1.4.0-SNAPSHOT</version>
99

1010
<name>openai-java-sdk</name>
1111
<description>

src/main/java/org/devlive/sdk/openai/DefaultApi.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,29 @@
11
package org.devlive.sdk.openai;
22

33
import io.reactivex.Single;
4+
import okhttp3.MultipartBody;
5+
import okhttp3.RequestBody;
46
import org.devlive.sdk.openai.entity.CompletionChatEntity;
57
import org.devlive.sdk.openai.entity.CompletionEntity;
8+
import org.devlive.sdk.openai.entity.ImageEntity;
69
import org.devlive.sdk.openai.entity.ModelEntity;
710
import org.devlive.sdk.openai.entity.UserKeyEntity;
811
import org.devlive.sdk.openai.response.CompleteChatResponse;
912
import org.devlive.sdk.openai.response.CompleteResponse;
13+
import org.devlive.sdk.openai.response.ImageResponse;
1014
import org.devlive.sdk.openai.response.ModelResponse;
1115
import org.devlive.sdk.openai.response.UserKeyResponse;
1216
import retrofit2.http.Body;
1317
import retrofit2.http.GET;
18+
import retrofit2.http.Multipart;
1419
import retrofit2.http.POST;
20+
import retrofit2.http.Part;
21+
import retrofit2.http.PartMap;
1522
import retrofit2.http.Path;
1623
import retrofit2.http.Url;
1724

25+
import java.util.Map;
26+
1827
public interface DefaultApi
1928
{
2029
/**
@@ -56,4 +65,30 @@ Single<CompleteChatResponse> fetchChatCompletions(@Url String url,
5665
*/
5766
@POST(value = "dashboard/user/api_keys")
5867
Single<UserKeyResponse> fetchCreateUserAPIKey(@Body UserKeyEntity configure);
68+
69+
/**
70+
* Creates an image given a prompt.
71+
*/
72+
@POST
73+
Single<ImageResponse> fetchImagesGenerations(@Url String url,
74+
@Body ImageEntity configure);
75+
76+
/**
77+
* Creates an edited or extended image given an original image and a prompt.
78+
*/
79+
@POST
80+
@Multipart
81+
Single<ImageResponse> fetchImagesEdits(@Url String url,
82+
@Part() MultipartBody.Part image,
83+
@Part() MultipartBody.Part mask,
84+
@PartMap Map<String, RequestBody> configure);
85+
86+
/**
87+
* Creates a variation of a given image.
88+
*/
89+
@POST
90+
@Multipart
91+
Single<ImageResponse> fetchImagesVariations(@Url String url,
92+
@Part() MultipartBody.Part image,
93+
@PartMap Map<String, RequestBody> configure);
5994
}

src/main/java/org/devlive/sdk/openai/DefaultClient.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,22 @@
11
package org.devlive.sdk.openai;
22

33
import lombok.extern.slf4j.Slf4j;
4+
import okhttp3.MultipartBody;
45
import okhttp3.OkHttpClient;
56
import org.apache.commons.lang3.ObjectUtils;
67
import org.devlive.sdk.openai.entity.CompletionChatEntity;
78
import org.devlive.sdk.openai.entity.CompletionEntity;
9+
import org.devlive.sdk.openai.entity.ImageEntity;
810
import org.devlive.sdk.openai.entity.ModelEntity;
911
import org.devlive.sdk.openai.entity.UserKeyEntity;
1012
import org.devlive.sdk.openai.model.ProviderModel;
1113
import org.devlive.sdk.openai.model.UrlModel;
1214
import org.devlive.sdk.openai.response.CompleteChatResponse;
1315
import org.devlive.sdk.openai.response.CompleteResponse;
16+
import org.devlive.sdk.openai.response.ImageResponse;
1417
import org.devlive.sdk.openai.response.ModelResponse;
1518
import org.devlive.sdk.openai.response.UserKeyResponse;
19+
import org.devlive.sdk.openai.utils.MultipartBodyUtils;
1620
import org.devlive.sdk.openai.utils.ProviderUtils;
1721

1822
@Slf4j
@@ -58,6 +62,37 @@ public UserKeyResponse createUserAPIKey(UserKeyEntity configure)
5862
.blockingGet();
5963
}
6064

65+
public ImageResponse createImages(ImageEntity configure)
66+
{
67+
configure.setIsVariation(null);
68+
configure.setIsEdit(null);
69+
return this.api.fetchImagesGenerations(ProviderUtils.getUrl(provider, UrlModel.FETCH_IMAGES_GENERATIONS), configure)
70+
.blockingGet();
71+
}
72+
73+
public ImageResponse editImages(ImageEntity configure)
74+
{
75+
MultipartBody.Part imageBody = MultipartBodyUtils.getPart(configure.getImage(), "image");
76+
MultipartBody.Part maskBody = null;
77+
if (ObjectUtils.isNotEmpty(configure.getMask())) {
78+
maskBody = MultipartBodyUtils.getPart(configure.getMask(), "mask");
79+
}
80+
return this.api.fetchImagesEdits(ProviderUtils.getUrl(provider, UrlModel.FETCH_IMAGES_EDITS),
81+
imageBody,
82+
maskBody,
83+
configure.convertMap())
84+
.blockingGet();
85+
}
86+
87+
public ImageResponse variationsImages(ImageEntity configure)
88+
{
89+
MultipartBody.Part imageBody = MultipartBodyUtils.getPart(configure.getImage(), "image");
90+
return this.api.fetchImagesVariations(ProviderUtils.getUrl(provider, UrlModel.FETCH_IMAGES_VARIATIONS),
91+
imageBody,
92+
configure.convertMap())
93+
.blockingGet();
94+
}
95+
6196
public void close()
6297
{
6398
if (ObjectUtils.isNotEmpty(this.client)) {
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
package org.devlive.sdk.openai.entity;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.google.common.collect.Maps;
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Data;
9+
import lombok.NoArgsConstructor;
10+
import lombok.ToString;
11+
import okhttp3.RequestBody;
12+
import org.apache.commons.lang3.EnumUtils;
13+
import org.apache.commons.lang3.ObjectUtils;
14+
import org.apache.commons.lang3.StringUtils;
15+
import org.devlive.sdk.openai.exception.ParamException;
16+
import org.devlive.sdk.openai.model.ImageFormatModel;
17+
import org.devlive.sdk.openai.model.ImageSizeModel;
18+
import org.devlive.sdk.openai.utils.MultipartBodyUtils;
19+
20+
import java.io.File;
21+
import java.util.Arrays;
22+
import java.util.Map;
23+
24+
@Data
25+
@Builder
26+
@ToString
27+
@NoArgsConstructor
28+
@AllArgsConstructor
29+
@JsonIgnoreProperties(ignoreUnknown = true)
30+
public class ImageEntity
31+
{
32+
/**
33+
* A text description of the desired image(s). The maximum length is 1000 characters.
34+
*/
35+
@JsonProperty(value = "prompt")
36+
private String prompt;
37+
38+
/**
39+
* The number of images to generate. Must be between 1 and 10.
40+
*/
41+
@JsonProperty(value = "n")
42+
private Integer count;
43+
44+
/**
45+
* The size of the generated images.
46+
*
47+
* @see ImageSizeModel
48+
*/
49+
@JsonProperty(value = "size")
50+
private String size;
51+
52+
/**
53+
* The format in which the generated images are returned.
54+
*
55+
* @see org.devlive.sdk.openai.model.ImageFormatModel
56+
*/
57+
@JsonProperty(value = "response_format")
58+
private String format = "url";
59+
60+
/**
61+
* A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse.
62+
*/
63+
@JsonProperty(value = "user")
64+
private String user;
65+
66+
@JsonProperty(value = "url")
67+
private String url;
68+
69+
@JsonProperty(value = "image")
70+
private File image;
71+
72+
@JsonProperty(value = "mask")
73+
private File mask;
74+
75+
private Boolean isEdit;
76+
private Boolean isVariation;
77+
78+
public Map<String, RequestBody> convertMap()
79+
{
80+
Map<String, RequestBody> map = Maps.newConcurrentMap();
81+
if (this.isEdit) {
82+
map.put("prompt", RequestBody.create(MultipartBodyUtils.TYPE, this.getPrompt()));
83+
}
84+
map.put("n", RequestBody.create(MultipartBodyUtils.TYPE, this.getCount().toString()));
85+
map.put("size", RequestBody.create(MultipartBodyUtils.TYPE, this.getSize()));
86+
map.put("response_format", RequestBody.create(MultipartBodyUtils.TYPE, this.getFormat()));
87+
88+
if (StringUtils.isNotEmpty(this.getUser())) {
89+
map.put("user", RequestBody.create(MultipartBodyUtils.TYPE, this.getUser()));
90+
}
91+
return map;
92+
}
93+
94+
private ImageEntity(ImageEntityBuilder builder)
95+
{
96+
if (ObjectUtils.isEmpty(builder.prompt)) {
97+
builder.prompt(null);
98+
}
99+
this.prompt = builder.prompt;
100+
101+
if (ObjectUtils.isEmpty(builder.count)) {
102+
builder.count(1);
103+
}
104+
this.count = builder.count;
105+
106+
if (ObjectUtils.isEmpty(builder.size)) {
107+
builder.size(ImageSizeModel.X_1024);
108+
}
109+
this.size = builder.size;
110+
111+
if (ObjectUtils.isEmpty(builder.format)) {
112+
builder.format(ImageFormatModel.url);
113+
}
114+
this.format = builder.format;
115+
116+
if (ObjectUtils.isEmpty(builder.image)) {
117+
builder.image(null);
118+
}
119+
this.image = builder.image;
120+
121+
if (ObjectUtils.isEmpty(builder.mask)) {
122+
builder.mask(null);
123+
}
124+
this.mask = builder.mask;
125+
126+
if (ObjectUtils.isEmpty(builder.isEdit)) {
127+
builder.isEdit(Boolean.FALSE);
128+
}
129+
this.isEdit = builder.isEdit;
130+
131+
if (ObjectUtils.isEmpty(builder.isVariation)) {
132+
builder.isVariation(Boolean.FALSE);
133+
}
134+
this.isVariation = builder.isVariation;
135+
136+
this.user = builder.user;
137+
}
138+
139+
public static class ImageEntityBuilder
140+
{
141+
public ImageEntityBuilder prompt(String prompt)
142+
{
143+
if ((ObjectUtils.isEmpty(this.isVariation) || !this.isVariation) && StringUtils.isEmpty(prompt)) {
144+
throw new ParamException("Invalid prompt must not be empty");
145+
}
146+
this.prompt = prompt;
147+
return this;
148+
}
149+
150+
public ImageEntityBuilder count(Integer count)
151+
{
152+
if (count < 1 || count > 10) {
153+
throw new ParamException(String.format("Invalid count: %s , between 1 and 10", count));
154+
}
155+
this.count = count;
156+
return this;
157+
}
158+
159+
public ImageEntityBuilder size(ImageSizeModel size)
160+
{
161+
Object instance = EnumUtils.getEnum(ImageSizeModel.class, size.name());
162+
if (ObjectUtils.isEmpty(instance)) {
163+
throw new ParamException(String.format("Invalid size: %s , Must be one of %s", size, Arrays.toString(ImageSizeModel.values())));
164+
}
165+
this.size = size.getName();
166+
return this;
167+
}
168+
169+
public ImageEntityBuilder format(ImageFormatModel format)
170+
{
171+
Object instance = EnumUtils.getEnum(ImageFormatModel.class, format.name());
172+
if (ObjectUtils.isEmpty(instance)) {
173+
throw new ParamException(String.format("Invalid format: %s , Must be one of %s", format, Arrays.toString(ImageFormatModel.values())));
174+
}
175+
this.format = format.name();
176+
return this;
177+
}
178+
179+
public ImageEntityBuilder image(File image)
180+
{
181+
if (ObjectUtils.isNotEmpty(image) && image.length() > 4 * 1024 * 102) {
182+
throw new ParamException("Must be less than 4MB");
183+
}
184+
this.image = image;
185+
return this;
186+
}
187+
188+
public ImageEntityBuilder mask(File mask)
189+
{
190+
if (ObjectUtils.isNotEmpty(mask) && mask.length() > 4 * 1024 * 102) {
191+
throw new ParamException("Must be less than 4MB");
192+
}
193+
this.mask = mask;
194+
return this;
195+
}
196+
197+
public ImageEntityBuilder isEdit(Boolean isEdit)
198+
{
199+
if (isEdit && ObjectUtils.isEmpty(this.image)) {
200+
throw new ParamException("Image must not be empty.");
201+
}
202+
this.isEdit = isEdit;
203+
return this;
204+
}
205+
206+
public ImageEntityBuilder isVariation(Boolean isVariation)
207+
{
208+
if (isVariation && ObjectUtils.isNotEmpty(this.prompt)) {
209+
throw new ParamException("Please remove prompt");
210+
}
211+
212+
if (isVariation && ObjectUtils.isEmpty(this.image)) {
213+
throw new ParamException("Image must not be empty.");
214+
}
215+
this.isVariation = isVariation;
216+
return this;
217+
}
218+
219+
public ImageEntity build()
220+
{
221+
return new ImageEntity(this);
222+
}
223+
}
224+
}

0 commit comments

Comments
 (0)