reve_ai 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +41 -0
- data/README.md +272 -2
- data/lib/reve_ai/client.rb +28 -1
- data/lib/reve_ai/configuration.rb +10 -1
- data/lib/reve_ai/errors.rb +15 -4
- data/lib/reve_ai/http/client.rb +100 -18
- data/lib/reve_ai/resources/base.rb +63 -7
- data/lib/reve_ai/resources/effects.rb +83 -0
- data/lib/reve_ai/resources/images.rb +100 -16
- data/lib/reve_ai/resources/v2/images.rb +188 -0
- data/lib/reve_ai/resources/v2/layouts.rb +309 -0
- data/lib/reve_ai/resources/v2.rb +42 -0
- data/lib/reve_ai/response.rb +108 -17
- data/lib/reve_ai/version.rb +1 -1
- data/lib/reve_ai.rb +4 -0
- metadata +22 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a51cf5f511364f9b5420edaba5d26f020c533354706dc0777fcd50a73ac8b99e
|
|
4
|
+
data.tar.gz: 15d846e877511007e0cf82ab1d447bf1915482dfa31210e8c4e7180aa75c6032
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b6af6528d75a2ab2fb007cf4e2e6277c722cc34d9ac396643e75bf3d36e09db4ff450764fc3f3c8d0cffa27f0a7e6cabc65f189a36c92d8b4ee8e313e257d4f2
|
|
7
|
+
data.tar.gz: 99194027f16869e1a2ecea152a030bb1a54f7d7e60b91824e608b7e9e41c5b82da12d5fdcdc633e54f5dd498a883e133ea3420725df8f9250593688bcb3728d4
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - Unreleased
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- API v2 image creation via `client.v2.images.create` (`POST /v2/image/create`): ordered `references`
|
|
13
|
+
(`{ data: }` / `{ ref: }` objects addressed from the prompt as `<frame>N</frame>`), the full
|
|
14
|
+
18-value aspect-ratio set including `auto`, prompts up to 4000 characters, and up to 8 references
|
|
15
|
+
- Experimental layout endpoints under `client.v2.layouts`: `extract` (`POST /v2/image/extract_layout`),
|
|
16
|
+
`create` (`POST /v2/image/create_layout`), and `render` (`POST /v2/image/render_layout`)
|
|
17
|
+
- Effects listing via `client.effects.list` (`GET /v1/image/effect`) with an optional `source` filter
|
|
18
|
+
- `postprocessing:`, `test_time_scaling:`, `accept:`, and `breadcrumb:` options on the v1
|
|
19
|
+
`create`/`edit`/`remix` and v2 endpoints
|
|
20
|
+
- Binary image responses: `accept: "image/png"`, `"image/jpeg"`, or `"image/webp"` returns raw image
|
|
21
|
+
bytes with metadata in `X-Reve-*` headers
|
|
22
|
+
- `ImageResponse#layout` and `ReveAI::LayoutResponse` for the structured layouts returned by v2 endpoints
|
|
23
|
+
- `APIError#params` for the error-specific `params` object returned by the API, and `APIError#error_code`
|
|
24
|
+
now falls back to the `X-Reve-Error-Code` header (binary error responses)
|
|
25
|
+
- GET support in the HTTP layer; retries now cover GET requests as well as POST
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Remix `<img>N</img>` YARD documentation corrected to 0-based indexing, matching the API
|
|
30
|
+
|
|
31
|
+
## [0.1.0] - 2026-01-04
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- Initial release
|
|
36
|
+
- `ReveAI::Client` for API authentication and configuration
|
|
37
|
+
- Image generation via `client.images.generate`
|
|
38
|
+
- Image editing via `client.images.edit`
|
|
39
|
+
- Image remix via `client.images.remix`
|
|
40
|
+
- Automatic retry with exponential backoff via Faraday
|
|
41
|
+
- Configurable timeouts and base URL
|
data/README.md
CHANGED
|
@@ -37,6 +37,18 @@ response.credits_used # => 18
|
|
|
37
37
|
response.credits_remaining # => 982
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
Save the image to a file:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
require "base64"
|
|
44
|
+
|
|
45
|
+
image_data = Base64.decode64(response.image)
|
|
46
|
+
|
|
47
|
+
File.open("output.png", "wb") do |f|
|
|
48
|
+
f.write(image_data)
|
|
49
|
+
end
|
|
50
|
+
```
|
|
51
|
+
|
|
40
52
|
With aspect ratio:
|
|
41
53
|
|
|
42
54
|
```ruby
|
|
@@ -76,7 +88,7 @@ response.image # => "base64editeddata..."
|
|
|
76
88
|
response.version # => "reve-edit@20250915"
|
|
77
89
|
```
|
|
78
90
|
|
|
79
|
-
Available versions for edit: `latest`, `latest-fast`, `reve-edit@20250915`, `reve-edit-fast@20251030`
|
|
91
|
+
Available versions for edit: `latest`, `latest-fast`, `reve-edit@20250915`, `reve-edit-fast@20251030`, `reve-edit-passthrough@20260625`
|
|
80
92
|
|
|
81
93
|
### Remix Images
|
|
82
94
|
|
|
@@ -104,6 +116,227 @@ response.version # => "reve-remix@20250915"
|
|
|
104
116
|
|
|
105
117
|
Available versions for remix: `latest`, `latest-fast`, `reve-remix@20250915`, `reve-remix-fast@20251030`
|
|
106
118
|
|
|
119
|
+
### Postprocessing and Effects
|
|
120
|
+
|
|
121
|
+
The v1 `create`/`edit`/`remix` methods and `client.v2.images.create` accept these optional keyword arguments (`client.v2.layouts.render` also supports `postprocessing:` and `accept:`, and every endpoint supports `breadcrumb:`):
|
|
122
|
+
|
|
123
|
+
| Option | Values | Description |
|
|
124
|
+
|--------|--------|-------------|
|
|
125
|
+
| `postprocessing:` | Array of operation Hashes | Operations applied to the generated image (see below) |
|
|
126
|
+
| `test_time_scaling:` | `1`-`15` | Spend more effort (and credits) on the request; clamped server-side. Not recommended for v2 models |
|
|
127
|
+
| `accept:` | `"image/png"`, `"image/jpeg"`, `"image/webp"` | Return raw image bytes instead of JSON; metadata moves to `X-Reve-*` headers |
|
|
128
|
+
| `breadcrumb:` | String | Request-tracking tag, searchable on the Reve Usage page; ignored by the API |
|
|
129
|
+
|
|
130
|
+
Supported postprocessing operations:
|
|
131
|
+
|
|
132
|
+
| Operation | Parameters | Notes |
|
|
133
|
+
|-----------|------------|-------|
|
|
134
|
+
| `upscale` | `upscale_factor` (integer, 1-4) | Adds credits cost; a 4x upscale is large |
|
|
135
|
+
| `remove_background` | none | Adds credits cost; works best with a clear subject |
|
|
136
|
+
| `fit_image` | `max_dim`, `max_width`, and/or `max_height` (max 4096) | Free; scales down preserving aspect ratio |
|
|
137
|
+
| `effect` | `effect_name`, optional `effect_parameters` | Applies an effect saved in your project |
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
response = client.images.create(
|
|
141
|
+
prompt: "A beautiful sunset over mountains",
|
|
142
|
+
postprocessing: [
|
|
143
|
+
{ process: "upscale", upscale_factor: 2 },
|
|
144
|
+
{ process: "fit_image", max_dim: 2048 }
|
|
145
|
+
],
|
|
146
|
+
test_time_scaling: 3,
|
|
147
|
+
breadcrumb: "homepage-hero"
|
|
148
|
+
)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Effect parameter overrides use a nested `{ filterId => { uniformId => value } }` format; omitted parameters fall back to the effect's saved defaults:
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
response = client.images.create(
|
|
155
|
+
prompt: "A high-quality photo of a wine bottle",
|
|
156
|
+
postprocessing: [
|
|
157
|
+
{
|
|
158
|
+
process: "effect",
|
|
159
|
+
effect_name: "adjustments",
|
|
160
|
+
effect_parameters: {
|
|
161
|
+
"adjustments" => {
|
|
162
|
+
"u_exposure" => 42,
|
|
163
|
+
"u_contrast" => -38,
|
|
164
|
+
"u_vibrance" => 64
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
List the effects available to your project — the returned `name` values are valid `effect_name` arguments:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
response = client.effects.list
|
|
176
|
+
|
|
177
|
+
response.body[:effects].each do |effect|
|
|
178
|
+
puts "#{effect[:name]} (#{effect[:source]})" # source: "saved" (project) or "builtin" (preset)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Optional source filter: "all" (default), "project" (saved only), "preset" (builtin only)
|
|
182
|
+
presets = client.effects.list(source: "preset")
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### API v2
|
|
186
|
+
|
|
187
|
+
The v2 API collapses the three v1 workflows into a single endpoint, `client.v2.images.create`, which takes ordered reference images and returns a structured `layout` alongside the image. The v1 endpoints above remain live and unchanged.
|
|
188
|
+
|
|
189
|
+
- Prompts up to 4000 characters, and up to 8 reference images
|
|
190
|
+
- The full 18-value aspect-ratio set, including `auto` (the default) — see [Validation Constraints](#validation-constraints)
|
|
191
|
+
- References are objects with exactly one of `data:` (base64-encoded image) or `ref:` (`"id:<uuid>"` for a stored image or generation, `"reference:@<name>"` for a named project reference)
|
|
192
|
+
- Address a reference from the prompt as `<frame>N</frame>` (0-based: the first reference is `<frame>0</frame>`)
|
|
193
|
+
- v2 images are significantly larger than v1 — cap the output with a free `fit_image` step: `postprocessing: [{ process: "fit_image", max_dim: 2048 }]`
|
|
194
|
+
|
|
195
|
+
Generate an image from text:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
response = client.v2.images.create(prompt: "A beautiful sunset over mountains")
|
|
199
|
+
|
|
200
|
+
response.image # => "base64encodeddata..."
|
|
201
|
+
response.version # => "latest" (v2 responses currently always report "latest")
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Edit an image (pass it as the first reference and address it as `<frame>0</frame>`):
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
require "base64"
|
|
208
|
+
|
|
209
|
+
image_data = Base64.strict_encode64(File.read("my-image.png"))
|
|
210
|
+
|
|
211
|
+
response = client.v2.images.create(
|
|
212
|
+
prompt: "Add dramatic clouds to the sky of <frame>0</frame>",
|
|
213
|
+
references: [{ data: image_data }]
|
|
214
|
+
)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Combine multiple references:
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
person = Base64.strict_encode64(File.read("person.png"))
|
|
221
|
+
background = Base64.strict_encode64(File.read("background.png"))
|
|
222
|
+
|
|
223
|
+
response = client.v2.images.create(
|
|
224
|
+
prompt: "The person from <frame>0</frame> standing in the scene from <frame>1</frame>",
|
|
225
|
+
references: [{ data: person }, { data: background }],
|
|
226
|
+
aspect_ratio: "21:9"
|
|
227
|
+
)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Every v2 JSON response includes the layout the model generated for the image:
|
|
231
|
+
|
|
232
|
+
```ruby
|
|
233
|
+
response = client.v2.images.create(prompt: "A serene mountain landscape at sunset")
|
|
234
|
+
|
|
235
|
+
response.layout # => { prompt: "...", regions: [...], width: 4672, height: 3520 }
|
|
236
|
+
|
|
237
|
+
response.layout[:regions].each do |region|
|
|
238
|
+
puts "#{region[:label]}: #{region[:prompt]}"
|
|
239
|
+
end
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Request a binary image instead of JSON with `accept:` — `response.image` then holds the raw bytes, and the metadata arrives via `X-Reve-*` headers:
|
|
243
|
+
|
|
244
|
+
```ruby
|
|
245
|
+
response = client.v2.images.create(
|
|
246
|
+
prompt: "A beautiful sunset over mountains",
|
|
247
|
+
accept: "image/webp"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
File.binwrite("mountains.webp", response.image) # no Base64 decoding needed
|
|
251
|
+
|
|
252
|
+
response.credits_used # => 150
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Pass `version:` to pin a model alias — v2 aliases observed: `latest` (default), `reve-v2-create@260601`.
|
|
256
|
+
|
|
257
|
+
#### Migrating from v1 to v2
|
|
258
|
+
|
|
259
|
+
All three v1 workflows map to `client.v2.images.create`:
|
|
260
|
+
|
|
261
|
+
Create — keep `prompt` and `aspect_ratio`:
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# v1
|
|
265
|
+
client.images.create(prompt: "A serene mountain landscape at sunset",
|
|
266
|
+
aspect_ratio: "16:9", version: "latest")
|
|
267
|
+
|
|
268
|
+
# v2
|
|
269
|
+
client.v2.images.create(prompt: "A serene mountain landscape at sunset",
|
|
270
|
+
aspect_ratio: "16:9")
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Edit — the edited image becomes `references[0]`, addressed as `<frame>0</frame>`:
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
# v1
|
|
277
|
+
client.images.edit(edit_instruction: "Remove the people in the background.",
|
|
278
|
+
reference_image: image_b64)
|
|
279
|
+
|
|
280
|
+
# v2
|
|
281
|
+
client.v2.images.create(prompt: "Remove the people in the background of <frame>0</frame>.",
|
|
282
|
+
references: [{ data: image_b64 }])
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Remix — `<img>N</img>` tags become `<frame>N</frame>` (both 0-based) and bare base64 strings become `{ data: }` objects:
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
# v1
|
|
289
|
+
client.images.remix(prompt: "The woman from <img>0</img> driving the car from <img>1</img>.",
|
|
290
|
+
reference_images: [woman_b64, car_b64], aspect_ratio: "1:1")
|
|
291
|
+
|
|
292
|
+
# v2
|
|
293
|
+
client.v2.images.create(prompt: "The woman from <frame>0</frame> driving the car from <frame>1</frame>.",
|
|
294
|
+
references: [{ data: woman_b64 }, { data: car_b64 }], aspect_ratio: "1:1")
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
#### Experimental Layout Endpoints
|
|
298
|
+
|
|
299
|
+
The layout endpoints are **experimental**: they require care and experimentation to achieve good results, are best suited for agents and custom tooling, and may change. For simple generation and prompt-based editing, use `client.v2.images.create`. They return JSON only (`render` also supports `accept:`) and commonly take 10-40 seconds (`render`: 40-80 seconds) — keep timeouts at 120 seconds or more.
|
|
300
|
+
|
|
301
|
+
A layout is a Hash with an optional overall `prompt`, optional `width`/`height`, and a `regions` array; each region carries a `label`, a regional `prompt`, and a normalized `bbox` (`x0`, `y0`, `x1`, `y1` in 0.0-1.0, top-left origin).
|
|
302
|
+
|
|
303
|
+
Extract a layout from an image:
|
|
304
|
+
|
|
305
|
+
```ruby
|
|
306
|
+
image = { data: Base64.strict_encode64(File.read("photo.jpg")) }
|
|
307
|
+
|
|
308
|
+
response = client.v2.layouts.extract(image: image)
|
|
309
|
+
response.layout # => { prompt: "...", regions: [...], width: 4672, height: 3520 }
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Create a layout from a prompt, without rendering an image:
|
|
313
|
+
|
|
314
|
+
```ruby
|
|
315
|
+
response = client.v2.layouts.create(prompt: "a person at a cafe", aspect_ratio: "3:2")
|
|
316
|
+
layout = response.layout
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Render an image from a layout:
|
|
320
|
+
|
|
321
|
+
```ruby
|
|
322
|
+
response = client.v2.layouts.render(layout: layout)
|
|
323
|
+
response.image # => "base64encodeddata..."
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Unlike v2 create, the layout endpoints take compound references — each entry may contain any subset of `image:`, `layout:`, and `prompt:`:
|
|
327
|
+
|
|
328
|
+
```ruby
|
|
329
|
+
response = client.v2.layouts.create(
|
|
330
|
+
prompt: "Put the bottle on a wooden table",
|
|
331
|
+
references: [
|
|
332
|
+
{ image: { data: bottle_b64 }, prompt: "the product to feature" },
|
|
333
|
+
{ layout: saved_layout }
|
|
334
|
+
]
|
|
335
|
+
)
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
`create` also accepts `commands:` — ordered imperative edits (`add`, `place`, `shift`, `remove`, `keep`, `change`) with normalized positions; see the [API docs](https://api.reve.com/console/docs) for the command shapes.
|
|
339
|
+
|
|
107
340
|
### Rails
|
|
108
341
|
|
|
109
342
|
Create `config/initializers/reve_ai.rb`:
|
|
@@ -155,6 +388,19 @@ rescue ReveAI::ConnectionError => e
|
|
|
155
388
|
end
|
|
156
389
|
```
|
|
157
390
|
|
|
391
|
+
Every API error also exposes the details returned by the API:
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
rescue ReveAI::APIError => e
|
|
395
|
+
e.error_code # => "PROMPT_TOO_LONG"
|
|
396
|
+
e.params # => Hash of error-specific parameters, or nil
|
|
397
|
+
e.status # => 400
|
|
398
|
+
e.request_id # => "rsid-..."
|
|
399
|
+
end
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
For binary image requests (`accept: "image/*"`), error responses carry a small grey image and the error code arrives in the `X-Reve-Error-Code` header — `#error_code` reads it from either the response body or the header.
|
|
403
|
+
|
|
158
404
|
### Content Moderation
|
|
159
405
|
|
|
160
406
|
The API may flag content violations:
|
|
@@ -173,20 +419,38 @@ end
|
|
|
173
419
|
|--------|---------|-------------|
|
|
174
420
|
| `api_key` | `ENV["REVE_AI_API_KEY"]` | Your Reve API key |
|
|
175
421
|
| `base_url` | `https://api.reve.com` | API base URL |
|
|
176
|
-
| `timeout` | `120` | Request timeout in seconds |
|
|
422
|
+
| `timeout` | `120` | Request timeout in seconds (the API requires at least 120) |
|
|
177
423
|
| `open_timeout` | `30` | Connection timeout in seconds |
|
|
178
424
|
| `max_retries` | `2` | Number of retries for failed requests |
|
|
179
425
|
| `logger` | `nil` | Logger instance for debugging |
|
|
180
426
|
| `debug` | `false` | Enable debug logging |
|
|
181
427
|
|
|
428
|
+
The API requires client timeouts of at least 120 seconds: image generation and rendering commonly take 40-80 seconds, and the layout endpoints 10-40 seconds. The gem's default of 120 seconds complies. Treat shorter client-side timeouts as cancellations — the server may still finish the request after the client disconnects.
|
|
429
|
+
|
|
182
430
|
### Validation Constraints
|
|
183
431
|
|
|
432
|
+
v1 endpoints (`create`, `edit`, `remix`):
|
|
433
|
+
|
|
184
434
|
| Constraint | Value |
|
|
185
435
|
|------------|-------|
|
|
186
436
|
| Max prompt length | 2560 characters |
|
|
187
437
|
| Max reference images (remix) | 6 |
|
|
188
438
|
| Valid aspect ratios | 16:9, 9:16, 3:2, 2:3, 4:3, 3:4, 1:1 |
|
|
189
439
|
|
|
440
|
+
v2 endpoints (`client.v2`):
|
|
441
|
+
|
|
442
|
+
| Constraint | Value |
|
|
443
|
+
|------------|-------|
|
|
444
|
+
| Max prompt length | 4000 characters |
|
|
445
|
+
| Max reference images | 8 |
|
|
446
|
+
| Valid aspect ratios | 4:1, 3:1, 21:9, 2:1, 17:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16, 1:2, 1:3, 1:4, auto (default: auto) |
|
|
447
|
+
|
|
448
|
+
Input image limits (any endpoint that accepts images):
|
|
449
|
+
|
|
450
|
+
- Formats: WEBP, JPEG, PNG, GIF, TIFF, AVIF — base64-encoded in JSON
|
|
451
|
+
- Per image: at most 40 MB and 33,554,432 pixels, with neither dimension exceeding 8192 pixels
|
|
452
|
+
- Per call: at most 100 MB and 50,331,648 pixels across all images
|
|
453
|
+
|
|
190
454
|
## Development
|
|
191
455
|
|
|
192
456
|
```
|
|
@@ -195,6 +459,12 @@ bundle exec rake test
|
|
|
195
459
|
bundle exec rubocop
|
|
196
460
|
```
|
|
197
461
|
|
|
462
|
+
## Release
|
|
463
|
+
|
|
464
|
+
```sh
|
|
465
|
+
bundle exec rake release
|
|
466
|
+
```
|
|
467
|
+
|
|
198
468
|
## Contributing
|
|
199
469
|
|
|
200
470
|
Bug reports and pull requests are welcome on GitHub at https://github.com/dpaluy/reve_ai.
|
data/lib/reve_ai/client.rb
CHANGED
|
@@ -64,13 +64,40 @@ module ReveAI
|
|
|
64
64
|
#
|
|
65
65
|
# @example Remix images
|
|
66
66
|
# result = client.images.remix(
|
|
67
|
-
# prompt: "Combine <img>
|
|
67
|
+
# prompt: "Combine <img>0</img> and <img>1</img> into one scene",
|
|
68
68
|
# reference_images: [image1_base64, image2_base64]
|
|
69
69
|
# )
|
|
70
70
|
def images
|
|
71
71
|
@images ||= Resources::Images.new(self)
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
# Returns the Effects resource for listing available effects.
|
|
75
|
+
#
|
|
76
|
+
# @return [Resources::Effects] Effects listing interface
|
|
77
|
+
# @see Resources::Effects
|
|
78
|
+
#
|
|
79
|
+
# @example List all effects available to the project
|
|
80
|
+
# result = client.effects.list
|
|
81
|
+
# result.body[:effects].each { |effect| puts effect[:name] }
|
|
82
|
+
def effects
|
|
83
|
+
@effects ||= Resources::Effects.new(self)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Returns the v2 API namespace (image create and layout endpoints).
|
|
87
|
+
#
|
|
88
|
+
# @return [Resources::V2] v2 API operations interface
|
|
89
|
+
# @see Resources::V2
|
|
90
|
+
#
|
|
91
|
+
# @example Generate an image with the v2 API
|
|
92
|
+
# result = client.v2.images.create(
|
|
93
|
+
# prompt: "Remove the people in the background of <frame>0</frame>.",
|
|
94
|
+
# references: [{ data: base64_encoded_image }]
|
|
95
|
+
# )
|
|
96
|
+
# result.layout # => { prompt: "...", regions: [...] }
|
|
97
|
+
def v2
|
|
98
|
+
@v2 ||= Resources::V2.new(self)
|
|
99
|
+
end
|
|
100
|
+
|
|
74
101
|
# Returns the HTTP client for making API requests.
|
|
75
102
|
#
|
|
76
103
|
# @return [HTTP::Client] HTTP client instance
|
|
@@ -34,7 +34,7 @@ module ReveAI
|
|
|
34
34
|
# @return [Integer] Default number of retry attempts for failed requests
|
|
35
35
|
DEFAULT_MAX_RETRIES = 2
|
|
36
36
|
|
|
37
|
-
# @return [Array<String>] Valid aspect ratios for image generation
|
|
37
|
+
# @return [Array<String>] Valid aspect ratios for legacy v1 image generation
|
|
38
38
|
VALID_ASPECT_RATIOS = %w[16:9 9:16 3:2 2:3 4:3 3:4 1:1].freeze
|
|
39
39
|
|
|
40
40
|
# @return [Integer] Maximum allowed prompt length in characters
|
|
@@ -43,6 +43,15 @@ module ReveAI
|
|
|
43
43
|
# @return [Integer] Maximum number of reference images for remix operations
|
|
44
44
|
MAX_REFERENCE_IMAGES = 6
|
|
45
45
|
|
|
46
|
+
# @return [Array<String>] Valid aspect ratios for image generation
|
|
47
|
+
ASPECT_RATIOS = %w[4:1 3:1 21:9 2:1 17:9 16:9 3:2 4:3 5:4 1:1 4:5 3:4 2:3 9:16 1:2 1:3 1:4 auto].freeze
|
|
48
|
+
|
|
49
|
+
# @return [Integer] Maximum allowed prompt length in characters for v2 endpoints
|
|
50
|
+
V2_MAX_PROMPT_LENGTH = 4000
|
|
51
|
+
|
|
52
|
+
# @return [Integer] Maximum number of references for v2 create operations
|
|
53
|
+
V2_MAX_REFERENCES = 8
|
|
54
|
+
|
|
46
55
|
# @return [String, nil] Reve API key for authentication
|
|
47
56
|
attr_accessor :api_key
|
|
48
57
|
|
data/lib/reve_ai/errors.rb
CHANGED
|
@@ -72,22 +72,27 @@ module ReveAI
|
|
|
72
72
|
# @return [Integer, nil] HTTP status code
|
|
73
73
|
attr_reader :status
|
|
74
74
|
|
|
75
|
-
# @return [Hash] Response body parsed as Hash
|
|
75
|
+
# @return [Hash, String] Response body parsed as Hash, or raw String
|
|
76
|
+
# for binary error responses (e.g., grey image bodies)
|
|
76
77
|
attr_reader :body
|
|
77
78
|
|
|
78
79
|
# @return [Hash] Response headers
|
|
79
80
|
attr_reader :headers
|
|
80
81
|
|
|
82
|
+
# @return [Hash, nil] Additional error details from the response body
|
|
83
|
+
attr_reader :params
|
|
84
|
+
|
|
81
85
|
# Creates a new API error instance.
|
|
82
86
|
#
|
|
83
87
|
# @param message [String, nil] Error message
|
|
84
88
|
# @param status [Integer, nil] HTTP status code
|
|
85
|
-
# @param body [Hash, nil] Response body
|
|
89
|
+
# @param body [Hash, String, nil] Response body
|
|
86
90
|
# @param headers [Hash, nil] Response headers
|
|
87
91
|
def initialize(message = nil, status: nil, body: nil, headers: nil)
|
|
88
92
|
@status = status
|
|
89
93
|
@body = body || {}
|
|
90
94
|
@headers = headers || {}
|
|
95
|
+
@params = @body.is_a?(Hash) ? @body[:params] : nil
|
|
91
96
|
super(message)
|
|
92
97
|
end
|
|
93
98
|
|
|
@@ -100,11 +105,17 @@ module ReveAI
|
|
|
100
105
|
headers["x-reve-request-id"]
|
|
101
106
|
end
|
|
102
107
|
|
|
103
|
-
# Returns the error code
|
|
108
|
+
# Returns the error code for this error.
|
|
109
|
+
#
|
|
110
|
+
# Read from the response body when present, falling back to the
|
|
111
|
+
# X-Reve-Error-Code header: with an image Accept header, the API answers
|
|
112
|
+
# errors with a small grey image body and no JSON error code.
|
|
104
113
|
#
|
|
105
114
|
# @return [String, nil] Error code (e.g., "PROMPT_TOO_LONG", "INVALID_API_KEY")
|
|
106
115
|
def error_code
|
|
107
|
-
body[:error_code]
|
|
116
|
+
return body[:error_code] if body.is_a?(Hash) && body[:error_code]
|
|
117
|
+
|
|
118
|
+
headers["x-reve-error-code"]
|
|
108
119
|
end
|
|
109
120
|
end
|
|
110
121
|
|
data/lib/reve_ai/http/client.rb
CHANGED
|
@@ -39,12 +39,46 @@ module ReveAI
|
|
|
39
39
|
@configuration = configuration
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
+
# Makes a GET request to the API.
|
|
43
|
+
#
|
|
44
|
+
# @param path [String] API endpoint path (e.g., "/v1/image/effect")
|
|
45
|
+
# @param params [Hash, nil] Query parameters to merge into the URL
|
|
46
|
+
# (e.g., { source: "project" })
|
|
47
|
+
#
|
|
48
|
+
# @return [Response] Parsed API response
|
|
49
|
+
#
|
|
50
|
+
# @raise [TimeoutError] if request times out
|
|
51
|
+
# @raise [ConnectionError] if connection fails
|
|
52
|
+
# @raise [NetworkError] for other network errors
|
|
53
|
+
# @raise [BadRequestError] on 400 responses
|
|
54
|
+
# @raise [UnauthorizedError] on 401 responses
|
|
55
|
+
# @raise [InsufficientCreditsError] on 402 responses
|
|
56
|
+
# @raise [ForbiddenError] on 403 responses
|
|
57
|
+
# @raise [NotFoundError] on 404 responses
|
|
58
|
+
# @raise [UnprocessableEntityError] on 422 responses
|
|
59
|
+
# @raise [RateLimitError] on 429 responses
|
|
60
|
+
# @raise [ServerError] on 5xx responses
|
|
61
|
+
#
|
|
62
|
+
# @api private
|
|
63
|
+
def get(path, params: nil)
|
|
64
|
+
normalized_path = path.sub(%r{^/}, "")
|
|
65
|
+
response = perform_request do |conn|
|
|
66
|
+
conn.get(normalized_path) { |req| req.params.update(params) if params }
|
|
67
|
+
end
|
|
68
|
+
handle_response(response)
|
|
69
|
+
end
|
|
70
|
+
|
|
42
71
|
# Makes a POST request to the API.
|
|
43
72
|
#
|
|
44
73
|
# @param path [String] API endpoint path (e.g., "/v1/image/create")
|
|
45
74
|
# @param body [Hash] Request body to send as JSON
|
|
75
|
+
# @param params [Hash, nil] Query parameters to merge into the URL
|
|
76
|
+
# (e.g., { breadcrumb: "my-tracking-value" })
|
|
77
|
+
# @param accept [String, nil] Per-request Accept header override
|
|
78
|
+
# (e.g., "image/webp"). The connection default stays "application/json".
|
|
46
79
|
#
|
|
47
|
-
# @return [Response] Parsed API response
|
|
80
|
+
# @return [Response] Parsed API response. When the response Content-Type
|
|
81
|
+
# is +image/*+, the body is the raw image String (bytes), not a Hash.
|
|
48
82
|
#
|
|
49
83
|
# @raise [TimeoutError] if request times out
|
|
50
84
|
# @raise [ConnectionError] if connection fails
|
|
@@ -59,10 +93,31 @@ module ReveAI
|
|
|
59
93
|
# @raise [ServerError] on 5xx responses
|
|
60
94
|
#
|
|
61
95
|
# @api private
|
|
62
|
-
def post(path, body = {})
|
|
96
|
+
def post(path, body = {}, params: nil, accept: nil)
|
|
63
97
|
normalized_path = path.sub(%r{^/}, "")
|
|
64
|
-
response =
|
|
98
|
+
response = perform_request do |conn|
|
|
99
|
+
conn.post(normalized_path) do |req|
|
|
100
|
+
req.params.update(params) if params
|
|
101
|
+
req.headers["Accept"] = accept if accept
|
|
102
|
+
req.body = JSON.generate(body)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
65
105
|
handle_response(response)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
private
|
|
109
|
+
|
|
110
|
+
# Performs an HTTP request, mapping Faraday errors to gem errors.
|
|
111
|
+
#
|
|
112
|
+
# @yieldparam connection [Faraday::Connection] Connection to perform the request on
|
|
113
|
+
# @return [Faraday::Response] Raw Faraday response
|
|
114
|
+
#
|
|
115
|
+
# @raise [TimeoutError] if request times out
|
|
116
|
+
# @raise [ConnectionError] if connection fails
|
|
117
|
+
# @raise [NetworkError] for other network errors
|
|
118
|
+
# @api private
|
|
119
|
+
def perform_request
|
|
120
|
+
yield connection
|
|
66
121
|
rescue Faraday::TimeoutError => e
|
|
67
122
|
raise TimeoutError, "Request timed out: #{e.message}"
|
|
68
123
|
rescue Faraday::ConnectionFailed => e
|
|
@@ -71,8 +126,6 @@ module ReveAI
|
|
|
71
126
|
raise NetworkError, "Network error: #{e.message}"
|
|
72
127
|
end
|
|
73
128
|
|
|
74
|
-
private
|
|
75
|
-
|
|
76
129
|
# Handles connection failed errors.
|
|
77
130
|
#
|
|
78
131
|
# Distinguishes between timeout errors (which may appear as connection failures)
|
|
@@ -116,7 +169,7 @@ module ReveAI
|
|
|
116
169
|
# @api private
|
|
117
170
|
def configure_retry(conn)
|
|
118
171
|
conn.request :retry, max: configuration.max_retries, interval: 0.5,
|
|
119
|
-
backoff_factor: 2, retry_statuses: RETRY_STATUSES, methods: [
|
|
172
|
+
backoff_factor: 2, retry_statuses: RETRY_STATUSES, methods: %i[post get]
|
|
120
173
|
end
|
|
121
174
|
|
|
122
175
|
# Configures request headers.
|
|
@@ -162,7 +215,7 @@ module ReveAI
|
|
|
162
215
|
# @raise [APIError] on error responses
|
|
163
216
|
# @api private
|
|
164
217
|
def handle_response(response)
|
|
165
|
-
body = parse_body(response
|
|
218
|
+
body = parse_body(response)
|
|
166
219
|
return build_success_response(response, body) if response.status.between?(200, 299)
|
|
167
220
|
|
|
168
221
|
raise_api_error(response.status, body, response.headers.to_h)
|
|
@@ -171,37 +224,66 @@ module ReveAI
|
|
|
171
224
|
# Builds a successful response object.
|
|
172
225
|
#
|
|
173
226
|
# @param response [Faraday::Response] Raw response
|
|
174
|
-
# @param body [Hash] Parsed body
|
|
227
|
+
# @param body [Hash, String] Parsed body, or raw bytes for binary responses
|
|
175
228
|
# @return [Response] Response wrapper
|
|
176
229
|
# @api private
|
|
177
230
|
def build_success_response(response, body)
|
|
178
231
|
Response.new(status: response.status, headers: response.headers.to_h, body: body)
|
|
179
232
|
end
|
|
180
233
|
|
|
181
|
-
# Parses the response body
|
|
234
|
+
# Parses the response body.
|
|
235
|
+
#
|
|
236
|
+
# Bodies with an +image/*+ Content-Type are raw image bytes and are
|
|
237
|
+
# returned as-is; anything else is parsed as JSON.
|
|
182
238
|
#
|
|
183
|
-
# @param
|
|
184
|
-
# @return [Hash] Parsed body,
|
|
239
|
+
# @param response [Faraday::Response] Raw Faraday response
|
|
240
|
+
# @return [Hash, String] Parsed body, raw bytes String for image
|
|
241
|
+
# responses, or empty hash if body is nil/empty
|
|
185
242
|
# @api private
|
|
186
|
-
def parse_body(
|
|
187
|
-
|
|
243
|
+
def parse_body(response)
|
|
244
|
+
raw = response.body
|
|
245
|
+
return {} if raw.nil? || raw.empty?
|
|
246
|
+
return raw if image_content?(response)
|
|
188
247
|
|
|
189
|
-
JSON.parse(
|
|
248
|
+
JSON.parse(raw, symbolize_names: true)
|
|
190
249
|
rescue JSON::ParserError
|
|
191
|
-
{ raw:
|
|
250
|
+
{ raw: raw }
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Checks whether the response carries raw image bytes.
|
|
254
|
+
#
|
|
255
|
+
# @param response [Faraday::Response] Raw Faraday response
|
|
256
|
+
# @return [Boolean] true if Content-Type starts with "image/"
|
|
257
|
+
# @api private
|
|
258
|
+
def image_content?(response)
|
|
259
|
+
response.headers["content-type"].to_s.start_with?("image/")
|
|
192
260
|
end
|
|
193
261
|
|
|
194
262
|
# Raises the appropriate API error for a status code.
|
|
195
263
|
#
|
|
196
264
|
# @param status [Integer] HTTP status code
|
|
197
|
-
# @param body [Hash] Parsed response body
|
|
265
|
+
# @param body [Hash, String] Parsed response body
|
|
198
266
|
# @param headers [Hash] Response headers
|
|
199
267
|
# @raise [APIError] Appropriate error subclass
|
|
200
268
|
# @api private
|
|
201
269
|
def raise_api_error(status, body, headers)
|
|
202
270
|
error_class = ERROR_CODE_MAP[status] || (status >= 500 ? ServerError : APIError)
|
|
203
|
-
|
|
204
|
-
|
|
271
|
+
raise error_class.new(extract_error_message(body, headers), status: status, body: body, headers: headers)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# Extracts the error message from the response body.
|
|
275
|
+
#
|
|
276
|
+
# Falls back to the X-Reve-Error-Code header when the body carries no
|
|
277
|
+
# message: with an image Accept header, the API answers errors with a
|
|
278
|
+
# small grey image instead of a JSON error body.
|
|
279
|
+
#
|
|
280
|
+
# @param body [Hash, String] Parsed response body
|
|
281
|
+
# @param headers [Hash] Response headers
|
|
282
|
+
# @return [String] Error message
|
|
283
|
+
# @api private
|
|
284
|
+
def extract_error_message(body, headers)
|
|
285
|
+
from_body = body.is_a?(Hash) ? body[:message] || body[:error] : nil
|
|
286
|
+
from_body || headers["x-reve-error-code"] || "Unknown error"
|
|
205
287
|
end
|
|
206
288
|
end
|
|
207
289
|
end
|