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
|
@@ -41,26 +41,41 @@ module ReveAI
|
|
|
41
41
|
client.configuration
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
# Makes a GET request to the API.
|
|
45
|
+
#
|
|
46
|
+
# @param path [String] API endpoint path
|
|
47
|
+
# @param params [Hash, nil] Query parameters to merge into the URL
|
|
48
|
+
# @return [Response] API response
|
|
49
|
+
# @api private
|
|
50
|
+
def get(path, params: nil)
|
|
51
|
+
http_client.get(path, params: params)
|
|
52
|
+
end
|
|
53
|
+
|
|
44
54
|
# Makes a POST request to the API.
|
|
45
55
|
#
|
|
46
56
|
# @param path [String] API endpoint path
|
|
47
57
|
# @param body [Hash] Request body
|
|
58
|
+
# @param params [Hash, nil] Query parameters to merge into the URL
|
|
59
|
+
# (e.g., { breadcrumb: "my-tracking-value" })
|
|
60
|
+
# @param accept [String, nil] Per-request Accept header override
|
|
61
|
+
# (e.g., "image/webp")
|
|
48
62
|
# @return [Response] API response
|
|
49
63
|
# @api private
|
|
50
|
-
def post(path, body = {})
|
|
51
|
-
http_client.post(path, body)
|
|
64
|
+
def post(path, body = {}, params: nil, accept: nil)
|
|
65
|
+
http_client.post(path, body, params: params, accept: accept)
|
|
52
66
|
end
|
|
53
67
|
|
|
54
68
|
# Validates a text prompt.
|
|
55
69
|
#
|
|
56
70
|
# @param prompt [String] The prompt to validate
|
|
57
71
|
# @param field_name [String] Name for error messages (default: "Prompt")
|
|
72
|
+
# @param max_length [Integer] Maximum allowed length in characters
|
|
73
|
+
# (default: v1 limit of {Configuration::MAX_PROMPT_LENGTH})
|
|
58
74
|
# @raise [ValidationError] if prompt is nil, empty, or exceeds max length
|
|
59
75
|
# @api private
|
|
60
|
-
def validate_prompt!(prompt, field_name: "Prompt")
|
|
76
|
+
def validate_prompt!(prompt, field_name: "Prompt", max_length: Configuration::MAX_PROMPT_LENGTH)
|
|
61
77
|
raise ValidationError, "#{field_name} is required" if prompt.nil? || prompt.empty?
|
|
62
78
|
|
|
63
|
-
max_length = Configuration::MAX_PROMPT_LENGTH
|
|
64
79
|
return unless prompt.length > max_length
|
|
65
80
|
|
|
66
81
|
raise ValidationError, "#{field_name} exceeds maximum length of #{max_length} characters"
|
|
@@ -69,17 +84,58 @@ module ReveAI
|
|
|
69
84
|
# Validates an aspect ratio value.
|
|
70
85
|
#
|
|
71
86
|
# @param aspect_ratio [String, nil] The aspect ratio to validate
|
|
87
|
+
# @param valid_ratios [Array<String>] Allowed aspect ratios
|
|
88
|
+
# (default: v1 set {Configuration::VALID_ASPECT_RATIOS})
|
|
72
89
|
# @raise [ValidationError] if aspect ratio is invalid
|
|
73
90
|
# @api private
|
|
74
|
-
def validate_aspect_ratio!(aspect_ratio)
|
|
91
|
+
def validate_aspect_ratio!(aspect_ratio, valid_ratios = Configuration::VALID_ASPECT_RATIOS)
|
|
75
92
|
return if aspect_ratio.nil?
|
|
76
|
-
|
|
77
|
-
valid_ratios = Configuration::VALID_ASPECT_RATIOS
|
|
78
93
|
return if valid_ratios.include?(aspect_ratio)
|
|
79
94
|
|
|
80
95
|
raise ValidationError, "Invalid aspect_ratio '#{aspect_ratio}'. Must be one of: #{valid_ratios.join(", ")}"
|
|
81
96
|
end
|
|
82
97
|
|
|
98
|
+
# Validates a postprocessing steps array.
|
|
99
|
+
#
|
|
100
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps, each
|
|
101
|
+
# requiring a +process+ key (e.g., { process: "upscale", upscale_factor: 2 })
|
|
102
|
+
# @raise [ValidationError] if not an Array of Hashes, or a step lacks a +process+ key
|
|
103
|
+
# @api private
|
|
104
|
+
def validate_postprocessing!(postprocessing)
|
|
105
|
+
return if postprocessing.nil?
|
|
106
|
+
|
|
107
|
+
unless postprocessing_steps?(postprocessing)
|
|
108
|
+
raise ValidationError, "Postprocessing must be an Array of Hashes with a 'process' key"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
postprocessing.each_with_index do |step, index|
|
|
112
|
+
next if step.key?(:process) || step.key?("process")
|
|
113
|
+
|
|
114
|
+
raise ValidationError, "Postprocessing step at index #{index} must include a 'process' key"
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Checks whether a value is an Array of Hashes (postprocessing shape).
|
|
119
|
+
#
|
|
120
|
+
# @param value [Object] The value to check
|
|
121
|
+
# @return [Boolean] true if value is an Array of Hashes
|
|
122
|
+
# @api private
|
|
123
|
+
def postprocessing_steps?(value)
|
|
124
|
+
value.is_a?(Array) && value.all?(Hash)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Validates a test_time_scaling value.
|
|
128
|
+
#
|
|
129
|
+
# @param value [Numeric, nil] Scaling factor (1-15)
|
|
130
|
+
# @raise [ValidationError] if not a Numeric between 1 and 15
|
|
131
|
+
# @api private
|
|
132
|
+
def validate_test_time_scaling!(value)
|
|
133
|
+
return if value.nil?
|
|
134
|
+
return if value.is_a?(Numeric) && value.between?(1, 15)
|
|
135
|
+
|
|
136
|
+
raise ValidationError, "test_time_scaling must be a number between 1 and 15"
|
|
137
|
+
end
|
|
138
|
+
|
|
83
139
|
# Validates a single reference image.
|
|
84
140
|
#
|
|
85
141
|
# @param image [String] Base64 encoded image data
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReveAI
|
|
4
|
+
module Resources
|
|
5
|
+
# Effects listing operations.
|
|
6
|
+
#
|
|
7
|
+
# Lists the effects available to the project associated with the API key,
|
|
8
|
+
# including saved project effects and built-in presets. Names from this
|
|
9
|
+
# list can be applied to any generation via the +postprocessing+ parameter
|
|
10
|
+
# of the image resources (see Images#create).
|
|
11
|
+
#
|
|
12
|
+
# @example List all effects
|
|
13
|
+
# client = ReveAI::Client.new(api_key: "your-key")
|
|
14
|
+
# result = client.effects.list
|
|
15
|
+
# result.body[:effects].each { |effect| puts effect[:name] }
|
|
16
|
+
#
|
|
17
|
+
# @note The list returns effect names only — effect parameter definitions
|
|
18
|
+
# are not included. Configure effect presets in the Reve application,
|
|
19
|
+
# save them with a name, and apply that name from the API.
|
|
20
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
21
|
+
class Effects < Base
|
|
22
|
+
# @return [String] API endpoint for listing effects
|
|
23
|
+
LIST_ENDPOINT = "/v1/image/effect"
|
|
24
|
+
|
|
25
|
+
# @return [Array<String>] Valid values for the +source+ filter
|
|
26
|
+
VALID_SOURCES = %w[all project preset].freeze
|
|
27
|
+
|
|
28
|
+
# Lists effects available to the project.
|
|
29
|
+
#
|
|
30
|
+
# The default response includes both saved project effects (+source+
|
|
31
|
+
# "saved") and built-in presets (+source+ "builtin"). Each entry in
|
|
32
|
+
# +body[:effects]+ carries +name+ and +source+, plus optional
|
|
33
|
+
# +description+ and +category+ (e.g., "color", "textures") when
|
|
34
|
+
# available. Use a returned +name+ as +effect_name+ in postprocessing
|
|
35
|
+
# requests.
|
|
36
|
+
#
|
|
37
|
+
# @param source [String, nil] Filter by effect origin: "all" (default),
|
|
38
|
+
# "project" (saved project effects only), or "preset" (builtin only)
|
|
39
|
+
# @param breadcrumb [String, nil] Request tracking label sent as the
|
|
40
|
+
# +breadcrumb+ query param; ignored by the API, searchable in the
|
|
41
|
+
# Usage page
|
|
42
|
+
#
|
|
43
|
+
# @return [Response] Response whose +body[:effects]+ is an Array of
|
|
44
|
+
# effect Hashes with +name+, +source+, and optional +description+
|
|
45
|
+
# and +category+ keys
|
|
46
|
+
#
|
|
47
|
+
# @raise [ValidationError] if source is not one of "all", "project", "preset"
|
|
48
|
+
# @raise [UnauthorizedError] if API key is invalid
|
|
49
|
+
# @raise [RateLimitError] if rate limit is exceeded
|
|
50
|
+
#
|
|
51
|
+
# @example List all effects
|
|
52
|
+
# result = client.effects.list
|
|
53
|
+
# result.body[:effects].map { |effect| effect[:name] }
|
|
54
|
+
#
|
|
55
|
+
# @example List only saved project effects
|
|
56
|
+
# result = client.effects.list(source: "project")
|
|
57
|
+
#
|
|
58
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
59
|
+
def list(source: nil, breadcrumb: nil)
|
|
60
|
+
validate_source!(source)
|
|
61
|
+
|
|
62
|
+
params = {}
|
|
63
|
+
params[:source] = source if source
|
|
64
|
+
params[:breadcrumb] = breadcrumb if breadcrumb
|
|
65
|
+
|
|
66
|
+
get(LIST_ENDPOINT, params: params.empty? ? nil : params)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Validates the source filter.
|
|
72
|
+
#
|
|
73
|
+
# @param source [String, nil] The source filter to validate
|
|
74
|
+
# @raise [ValidationError] if source is not one of {VALID_SOURCES}
|
|
75
|
+
# @api private
|
|
76
|
+
def validate_source!(source)
|
|
77
|
+
return if source.nil? || VALID_SOURCES.include?(source)
|
|
78
|
+
|
|
79
|
+
raise ValidationError, "Invalid source '#{source}'. Must be one of: #{VALID_SOURCES.join(", ")}"
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -23,11 +23,13 @@ module ReveAI
|
|
|
23
23
|
#
|
|
24
24
|
# @example Remix multiple images
|
|
25
25
|
# result = client.images.remix(
|
|
26
|
-
# prompt: "Combine the style of <img>
|
|
26
|
+
# prompt: "Combine the style of <img>0</img> with the subject of <img>1</img>",
|
|
27
27
|
# reference_images: [style_image_base64, subject_image_base64]
|
|
28
28
|
# )
|
|
29
29
|
#
|
|
30
|
-
# @note
|
|
30
|
+
# @note By default all images are returned as base64 encoded PNG data;
|
|
31
|
+
# pass accept: "image/png", "image/jpeg", or "image/webp" to any method
|
|
32
|
+
# for raw image bytes instead (see method docs).
|
|
31
33
|
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
32
34
|
class Images < Base
|
|
33
35
|
# @return [String] API endpoint for image creation
|
|
@@ -44,6 +46,22 @@ module ReveAI
|
|
|
44
46
|
# @param prompt [String] Text description of the desired image (max 2560 chars)
|
|
45
47
|
# @param aspect_ratio [String, nil] Output aspect ratio (defaults to API default)
|
|
46
48
|
# @param version [String, nil] Model version to use (defaults to "latest")
|
|
49
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps applied after
|
|
50
|
+
# generation; each step requires a +process+ key:
|
|
51
|
+
# +upscale+ (+upscale_factor+ 2-4, adds credits cost),
|
|
52
|
+
# +remove_background+,
|
|
53
|
+
# +fit_image+ (+max_dim+/+max_width+/+max_height+, max 4096, free),
|
|
54
|
+
# +effect+ (+effect_name+, optional +effect_parameters+ overrides nested
|
|
55
|
+
# as +{ filter_id: { uniform_id: value } }+)
|
|
56
|
+
# @param test_time_scaling [Numeric, nil] Effort scaling factor 1-15
|
|
57
|
+
# (default 1); values above 1 add credits cost, values above 5 only
|
|
58
|
+
# occasionally improve results
|
|
59
|
+
# @param accept [String, nil] Response format: "application/json" (default)
|
|
60
|
+
# or "image/png", "image/jpeg", "image/webp" for raw image bytes, e.g.
|
|
61
|
+
# accept: "image/webp" returns the raw image via result.image (metadata
|
|
62
|
+
# moves to the X-Reve-* response headers)
|
|
63
|
+
# @param breadcrumb [String, nil] Request tracking label sent as the
|
|
64
|
+
# +breadcrumb+ query param; ignored by the API, searchable in the Usage page
|
|
47
65
|
#
|
|
48
66
|
# @option aspect_ratio [String] "16:9" Widescreen landscape
|
|
49
67
|
# @option aspect_ratio [String] "9:16" Portrait (phone)
|
|
@@ -53,10 +71,13 @@ module ReveAI
|
|
|
53
71
|
# @option aspect_ratio [String] "3:4" Standard portrait
|
|
54
72
|
# @option aspect_ratio [String] "1:1" Square
|
|
55
73
|
#
|
|
56
|
-
# @return [ImageResponse] Response containing base64 encoded image
|
|
74
|
+
# @return [ImageResponse] Response containing base64 encoded image,
|
|
75
|
+
# or raw image bytes when +accept+ is an image format
|
|
57
76
|
#
|
|
58
77
|
# @raise [ValidationError] if prompt is empty or exceeds max length
|
|
59
78
|
# @raise [ValidationError] if aspect_ratio is invalid
|
|
79
|
+
# @raise [ValidationError] if postprocessing is not an Array of Hashes with a +process+ key
|
|
80
|
+
# @raise [ValidationError] if test_time_scaling is not a number between 1 and 15
|
|
60
81
|
# @raise [BadRequestError] if API rejects the request
|
|
61
82
|
# @raise [UnauthorizedError] if API key is invalid
|
|
62
83
|
# @raise [InsufficientCreditsError] if account has no credits
|
|
@@ -71,20 +92,38 @@ module ReveAI
|
|
|
71
92
|
# aspect_ratio: "16:9"
|
|
72
93
|
# )
|
|
73
94
|
#
|
|
95
|
+
# @example With postprocessing (upscale, then fit within 2048px)
|
|
96
|
+
# result = client.images.create(
|
|
97
|
+
# prompt: "A panoramic mountain landscape",
|
|
98
|
+
# postprocessing: [{ process: "upscale", upscale_factor: 2 },
|
|
99
|
+
# { process: "fit_image", max_dim: 2048 }]
|
|
100
|
+
# )
|
|
101
|
+
#
|
|
102
|
+
# @example Request raw WebP bytes instead of JSON
|
|
103
|
+
# result = client.images.create(prompt: "A sunset", accept: "image/webp")
|
|
104
|
+
# File.binwrite("sunset.webp", result.image) # raw bytes, no Base64 decode
|
|
105
|
+
#
|
|
74
106
|
# @example Save to file
|
|
75
107
|
# result = client.images.create(prompt: "A sunset")
|
|
76
108
|
# File.binwrite("image.png", Base64.decode64(result.base64))
|
|
77
109
|
#
|
|
78
110
|
# @see https://api.reve.com/console/docs#/Image/create_v1_image_create_post
|
|
79
|
-
def create(prompt:, aspect_ratio: nil, version: nil
|
|
111
|
+
def create(prompt:, aspect_ratio: nil, version: nil, postprocessing: nil, test_time_scaling: nil,
|
|
112
|
+
accept: nil, breadcrumb: nil)
|
|
80
113
|
validate_prompt!(prompt)
|
|
81
114
|
validate_aspect_ratio!(aspect_ratio)
|
|
115
|
+
validate_postprocessing!(postprocessing)
|
|
116
|
+
validate_test_time_scaling!(test_time_scaling)
|
|
82
117
|
|
|
83
118
|
body = { prompt: prompt }
|
|
84
119
|
body[:aspect_ratio] = aspect_ratio if aspect_ratio
|
|
85
120
|
body[:version] = version if version
|
|
121
|
+
body[:postprocessing] = postprocessing if postprocessing
|
|
122
|
+
body[:test_time_scaling] = test_time_scaling if test_time_scaling
|
|
123
|
+
|
|
124
|
+
params = breadcrumb ? { breadcrumb: breadcrumb } : nil
|
|
86
125
|
|
|
87
|
-
response = post(CREATE_ENDPOINT, body)
|
|
126
|
+
response = post(CREATE_ENDPOINT, body, params: params, accept: accept)
|
|
88
127
|
ImageResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
89
128
|
end
|
|
90
129
|
|
|
@@ -94,12 +133,24 @@ module ReveAI
|
|
|
94
133
|
# @param reference_image [String] Base64 encoded image to edit
|
|
95
134
|
# @param aspect_ratio [String, nil] Output aspect ratio (defaults to reference image ratio)
|
|
96
135
|
# @param version [String, nil] Model version to use (defaults to "latest")
|
|
136
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps applied after
|
|
137
|
+
# generation; see {#create} for the supported step shapes
|
|
138
|
+
# @param test_time_scaling [Numeric, nil] Effort scaling factor 1-15
|
|
139
|
+
# (default 1); values above 1 add credits cost
|
|
140
|
+
# @param accept [String, nil] Response format: "application/json" (default)
|
|
141
|
+
# or "image/png", "image/jpeg", "image/webp" for raw image bytes via
|
|
142
|
+
# result.image (metadata moves to the X-Reve-* response headers)
|
|
143
|
+
# @param breadcrumb [String, nil] Request tracking label sent as the
|
|
144
|
+
# +breadcrumb+ query param; ignored by the API, searchable in the Usage page
|
|
97
145
|
#
|
|
98
|
-
# @return [ImageResponse] Response containing base64 encoded edited image
|
|
146
|
+
# @return [ImageResponse] Response containing base64 encoded edited image,
|
|
147
|
+
# or raw image bytes when +accept+ is an image format
|
|
99
148
|
#
|
|
100
149
|
# @raise [ValidationError] if edit_instruction is empty or exceeds max length
|
|
101
150
|
# @raise [ValidationError] if reference_image is empty
|
|
102
151
|
# @raise [ValidationError] if aspect_ratio is invalid
|
|
152
|
+
# @raise [ValidationError] if postprocessing is not an Array of Hashes with a +process+ key
|
|
153
|
+
# @raise [ValidationError] if test_time_scaling is not a number between 1 and 15
|
|
103
154
|
# @raise [UnprocessableEntityError] if reference_image is not valid base64
|
|
104
155
|
# @raise [BadRequestError] if API rejects the request
|
|
105
156
|
# @raise [UnauthorizedError] if API key is invalid
|
|
@@ -116,67 +167,100 @@ module ReveAI
|
|
|
116
167
|
# reference_image: landscape_base64
|
|
117
168
|
# )
|
|
118
169
|
#
|
|
170
|
+
# @example Remove the background after editing
|
|
171
|
+
# result = client.images.edit(
|
|
172
|
+
# edit_instruction: "Change the car color from red to blue",
|
|
173
|
+
# reference_image: original_image_base64,
|
|
174
|
+
# postprocessing: [{ process: "remove_background" }]
|
|
175
|
+
# )
|
|
176
|
+
#
|
|
119
177
|
# @see https://api.reve.com/console/docs#/Image/edit_v1_image_edit_post
|
|
120
|
-
def edit(edit_instruction:, reference_image:, aspect_ratio: nil, version: nil
|
|
178
|
+
def edit(edit_instruction:, reference_image:, aspect_ratio: nil, version: nil, postprocessing: nil,
|
|
179
|
+
test_time_scaling: nil, accept: nil, breadcrumb: nil)
|
|
121
180
|
validate_prompt!(edit_instruction, field_name: "Edit instruction")
|
|
122
181
|
validate_reference_image!(reference_image)
|
|
182
|
+
validate_postprocessing!(postprocessing)
|
|
183
|
+
validate_test_time_scaling!(test_time_scaling)
|
|
123
184
|
|
|
124
185
|
body = { edit_instruction: edit_instruction, reference_image: reference_image }
|
|
125
186
|
body[:aspect_ratio] = aspect_ratio if aspect_ratio
|
|
126
187
|
body[:version] = version if version
|
|
188
|
+
body[:postprocessing] = postprocessing if postprocessing
|
|
189
|
+
body[:test_time_scaling] = test_time_scaling if test_time_scaling
|
|
190
|
+
|
|
191
|
+
params = breadcrumb ? { breadcrumb: breadcrumb } : nil
|
|
127
192
|
|
|
128
|
-
response = post(EDIT_ENDPOINT, body)
|
|
193
|
+
response = post(EDIT_ENDPOINT, body, params: params, accept: accept)
|
|
129
194
|
ImageResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
130
195
|
end
|
|
131
196
|
|
|
132
197
|
# Creates a new image by remixing multiple reference images.
|
|
133
198
|
#
|
|
134
199
|
# Use `<img>N</img>` tags in the prompt to reference specific images,
|
|
135
|
-
# where N is the
|
|
200
|
+
# where N is the 0-based index into the reference_images array.
|
|
136
201
|
#
|
|
137
202
|
# @param prompt [String] Text description with optional image references (max 2560 chars)
|
|
138
203
|
# @param reference_images [Array<String>] Array of base64 encoded images (1-6 images)
|
|
139
204
|
# @param aspect_ratio [String, nil] Output aspect ratio (defaults to model's choice)
|
|
140
205
|
# @param version [String, nil] Model version to use (defaults to "latest")
|
|
206
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps applied after
|
|
207
|
+
# generation; see {#create} for the supported step shapes
|
|
208
|
+
# @param test_time_scaling [Numeric, nil] Effort scaling factor 1-15
|
|
209
|
+
# (default 1); values above 1 add credits cost
|
|
210
|
+
# @param accept [String, nil] Response format: "application/json" (default)
|
|
211
|
+
# or "image/png", "image/jpeg", "image/webp" for raw image bytes via
|
|
212
|
+
# result.image (metadata moves to the X-Reve-* response headers)
|
|
213
|
+
# @param breadcrumb [String, nil] Request tracking label sent as the
|
|
214
|
+
# +breadcrumb+ query param; ignored by the API, searchable in the Usage page
|
|
141
215
|
#
|
|
142
|
-
# @return [ImageResponse] Response containing base64 encoded remixed image
|
|
216
|
+
# @return [ImageResponse] Response containing base64 encoded remixed image,
|
|
217
|
+
# or raw image bytes when +accept+ is an image format
|
|
143
218
|
#
|
|
144
219
|
# @raise [ValidationError] if prompt is empty or exceeds max length
|
|
145
220
|
# @raise [ValidationError] if reference_images is empty or exceeds 6 images
|
|
146
221
|
# @raise [ValidationError] if any reference image is empty
|
|
147
222
|
# @raise [ValidationError] if aspect_ratio is invalid
|
|
223
|
+
# @raise [ValidationError] if postprocessing is not an Array of Hashes with a +process+ key
|
|
224
|
+
# @raise [ValidationError] if test_time_scaling is not a number between 1 and 15
|
|
148
225
|
# @raise [BadRequestError] if API rejects the request
|
|
149
226
|
#
|
|
150
227
|
# @example Combine two images
|
|
151
228
|
# result = client.images.remix(
|
|
152
|
-
# prompt: "Combine the landscape from <img>
|
|
229
|
+
# prompt: "Combine the landscape from <img>0</img> with the sky from <img>1</img>",
|
|
153
230
|
# reference_images: [landscape_base64, sky_base64]
|
|
154
231
|
# )
|
|
155
232
|
#
|
|
156
233
|
# @example Style transfer
|
|
157
234
|
# result = client.images.remix(
|
|
158
|
-
# prompt: "Apply the artistic style of <img>
|
|
235
|
+
# prompt: "Apply the artistic style of <img>0</img> to the photo <img>1</img>",
|
|
159
236
|
# reference_images: [artwork_base64, photo_base64]
|
|
160
237
|
# )
|
|
161
238
|
#
|
|
162
239
|
# @example Multiple references
|
|
163
240
|
# result = client.images.remix(
|
|
164
|
-
# prompt: "Create a scene with the dog from <img>
|
|
165
|
-
# "the background from <img>
|
|
241
|
+
# prompt: "Create a scene with the dog from <img>0</img>, " \
|
|
242
|
+
# "the background from <img>1</img>, and lighting from <img>2</img>",
|
|
166
243
|
# reference_images: [dog_base64, background_base64, lighting_ref_base64]
|
|
167
244
|
# )
|
|
168
245
|
#
|
|
169
246
|
# @see https://api.reve.com/console/docs#/Image/remix_v1_image_remix_post
|
|
170
|
-
def remix(prompt:, reference_images:, aspect_ratio: nil, version: nil
|
|
247
|
+
def remix(prompt:, reference_images:, aspect_ratio: nil, version: nil, postprocessing: nil,
|
|
248
|
+
test_time_scaling: nil, accept: nil, breadcrumb: nil)
|
|
171
249
|
validate_prompt!(prompt)
|
|
172
250
|
validate_reference_images!(reference_images)
|
|
173
251
|
validate_aspect_ratio!(aspect_ratio)
|
|
252
|
+
validate_postprocessing!(postprocessing)
|
|
253
|
+
validate_test_time_scaling!(test_time_scaling)
|
|
174
254
|
|
|
175
255
|
body = { prompt: prompt, reference_images: reference_images }
|
|
176
256
|
body[:aspect_ratio] = aspect_ratio if aspect_ratio
|
|
177
257
|
body[:version] = version if version
|
|
258
|
+
body[:postprocessing] = postprocessing if postprocessing
|
|
259
|
+
body[:test_time_scaling] = test_time_scaling if test_time_scaling
|
|
260
|
+
|
|
261
|
+
params = breadcrumb ? { breadcrumb: breadcrumb } : nil
|
|
178
262
|
|
|
179
|
-
response = post(REMIX_ENDPOINT, body)
|
|
263
|
+
response = post(REMIX_ENDPOINT, body, params: params, accept: accept)
|
|
180
264
|
ImageResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
181
265
|
end
|
|
182
266
|
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
|
|
5
|
+
module ReveAI
|
|
6
|
+
module Resources
|
|
7
|
+
class V2
|
|
8
|
+
# v2 image generation operations.
|
|
9
|
+
#
|
|
10
|
+
# The v2 create endpoint unifies the v1 create, edit, and remix
|
|
11
|
+
# workflows: a single prompt plus an ordered list of reference images.
|
|
12
|
+
# JSON responses include a structured +layout+ alongside the image.
|
|
13
|
+
#
|
|
14
|
+
# @example Generate an image from text
|
|
15
|
+
# result = client.v2.images.create(prompt: "A sunset over mountains")
|
|
16
|
+
# result.base64 # Base64 encoded PNG
|
|
17
|
+
# result.layout # => { prompt: "...", regions: [...] }
|
|
18
|
+
#
|
|
19
|
+
# @note Image generation requests commonly take 40-80 seconds; the API
|
|
20
|
+
# documentation mandates request timeouts of at least 120 seconds
|
|
21
|
+
# (the gem default).
|
|
22
|
+
#
|
|
23
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
24
|
+
class Images < Base
|
|
25
|
+
# @return [String] API endpoint for v2 image creation
|
|
26
|
+
CREATE_ENDPOINT = "/v2/image/create"
|
|
27
|
+
|
|
28
|
+
# Generates an image from a text prompt with optional reference images.
|
|
29
|
+
#
|
|
30
|
+
# Reference images are addressed from the prompt with `<frame>N</frame>`
|
|
31
|
+
# tags, where N is the 0-based index into the +references+ array (so
|
|
32
|
+
# the first reference is `<frame>0</frame>`).
|
|
33
|
+
#
|
|
34
|
+
# @param prompt [String] Text description of the desired image
|
|
35
|
+
# (max 4000 chars); may contain `<frame>N</frame>` reference tags
|
|
36
|
+
# @param references [Array<Hash>, nil] Up to 8 reference images
|
|
37
|
+
# ({Configuration::V2_MAX_REFERENCES}). Each entry is a Hash with
|
|
38
|
+
# exactly one of +data+ (base64 encoded image String) or +ref+
|
|
39
|
+
# (identifier String: +"id:<uuid>"+ for a previously stored image or
|
|
40
|
+
# generation, +"reference:@<name>"+ for a named reference in your
|
|
41
|
+
# project). Entries are serialized as given.
|
|
42
|
+
# @param aspect_ratio [String, nil] Output aspect ratio (defaults to
|
|
43
|
+
# the API default of "auto", which lets the model pick); v2 supports
|
|
44
|
+
# the full set in {Configuration::ASPECT_RATIOS}, including "auto"
|
|
45
|
+
# and "4:1"
|
|
46
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps, each
|
|
47
|
+
# with a +process+ key (e.g., { process: "upscale", upscale_factor: 2 })
|
|
48
|
+
# @param test_time_scaling [Numeric, nil] Scaling factor (1-15);
|
|
49
|
+
# values above 1 cost more credits
|
|
50
|
+
# @param version [String, nil] Optional public model version alias,
|
|
51
|
+
# passed through as-is (e.g., "latest", "reve-v2-create@260601")
|
|
52
|
+
# @param accept [String, nil] Per-request Accept header: "image/png",
|
|
53
|
+
# "image/jpeg", or "image/webp" for a binary image response, or
|
|
54
|
+
# "application/json" (default)
|
|
55
|
+
# @param breadcrumb [String, nil] Request tracking value sent as the
|
|
56
|
+
# +breadcrumb+ query parameter; ignored by the API
|
|
57
|
+
#
|
|
58
|
+
# @return [ImageResponse] Response containing the image and, on JSON
|
|
59
|
+
# responses, the generated +layout+
|
|
60
|
+
#
|
|
61
|
+
# @raise [ValidationError] if prompt is empty or exceeds 4000 characters
|
|
62
|
+
# @raise [ValidationError] if references is malformed or exceeds 8 entries
|
|
63
|
+
# @raise [ValidationError] if aspect_ratio is invalid
|
|
64
|
+
# @raise [ValidationError] if postprocessing or test_time_scaling is invalid
|
|
65
|
+
# @raise [BadRequestError] if API rejects the request
|
|
66
|
+
# @raise [UnauthorizedError] if API key is invalid
|
|
67
|
+
# @raise [InsufficientCreditsError] if account has no credits
|
|
68
|
+
# @raise [RateLimitError] if rate limit is exceeded
|
|
69
|
+
#
|
|
70
|
+
# @example Text-to-image (no references)
|
|
71
|
+
# result = client.v2.images.create(
|
|
72
|
+
# prompt: "A serene mountain landscape at sunset",
|
|
73
|
+
# aspect_ratio: "16:9"
|
|
74
|
+
# )
|
|
75
|
+
#
|
|
76
|
+
# @example Edit-style: one reference addressed as <frame>0</frame>
|
|
77
|
+
# result = client.v2.images.create(
|
|
78
|
+
# prompt: "Remove the people in the background of <frame>0</frame>.",
|
|
79
|
+
# references: [{ data: original_image_base64 }]
|
|
80
|
+
# )
|
|
81
|
+
#
|
|
82
|
+
# @example Remix-style: combine two references
|
|
83
|
+
# result = client.v2.images.create(
|
|
84
|
+
# prompt: "The woman from <frame>0</frame> driving the car from <frame>1</frame>.",
|
|
85
|
+
# references: [{ data: woman_base64 }, { data: car_base64 }]
|
|
86
|
+
# )
|
|
87
|
+
#
|
|
88
|
+
# @note v2 images are significantly larger than v1 images; the API
|
|
89
|
+
# documentation suggests capping the output size with
|
|
90
|
+
# +postprocessing: [{ process: "fit_image", max_dim: 2048 }]+.
|
|
91
|
+
# @note The API documentation does not recommend +test_time_scaling+
|
|
92
|
+
# for v2 models.
|
|
93
|
+
#
|
|
94
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
95
|
+
def create(prompt:, references: nil, aspect_ratio: nil, postprocessing: nil,
|
|
96
|
+
test_time_scaling: nil, version: nil, accept: nil, breadcrumb: nil)
|
|
97
|
+
validate_prompt!(prompt, max_length: Configuration::V2_MAX_PROMPT_LENGTH)
|
|
98
|
+
validate_references!(references)
|
|
99
|
+
validate_aspect_ratio!(aspect_ratio, Configuration::ASPECT_RATIOS)
|
|
100
|
+
validate_postprocessing!(postprocessing)
|
|
101
|
+
validate_test_time_scaling!(test_time_scaling)
|
|
102
|
+
|
|
103
|
+
body = build_create_body(prompt: prompt, references: references, aspect_ratio: aspect_ratio,
|
|
104
|
+
postprocessing: postprocessing, test_time_scaling: test_time_scaling,
|
|
105
|
+
version: version)
|
|
106
|
+
params = breadcrumb ? { breadcrumb: breadcrumb } : nil
|
|
107
|
+
|
|
108
|
+
response = post(CREATE_ENDPOINT, body, params: params, accept: accept)
|
|
109
|
+
ImageResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Builds the request body for the create endpoint.
|
|
115
|
+
#
|
|
116
|
+
# @return [Hash] Request body with only the provided options
|
|
117
|
+
# @api private
|
|
118
|
+
def build_create_body(prompt:, references:, aspect_ratio:, postprocessing:, test_time_scaling:, version:)
|
|
119
|
+
body = { prompt: prompt }
|
|
120
|
+
body[:references] = references if references
|
|
121
|
+
body[:aspect_ratio] = aspect_ratio if aspect_ratio
|
|
122
|
+
body[:postprocessing] = postprocessing if postprocessing
|
|
123
|
+
body[:test_time_scaling] = test_time_scaling if test_time_scaling
|
|
124
|
+
body[:version] = version if version
|
|
125
|
+
body
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Validates the references array.
|
|
129
|
+
#
|
|
130
|
+
# @param references [Array<Hash>, nil] Reference image entries
|
|
131
|
+
# @raise [ValidationError] if not an Array, exceeds the maximum, or
|
|
132
|
+
# contains a malformed entry
|
|
133
|
+
# @api private
|
|
134
|
+
def validate_references!(references)
|
|
135
|
+
return if references.nil?
|
|
136
|
+
|
|
137
|
+
unless references.is_a?(Array)
|
|
138
|
+
raise ValidationError, "References must be an Array of Hashes with exactly one of 'data' or 'ref'"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
max = Configuration::V2_MAX_REFERENCES
|
|
142
|
+
raise ValidationError, "Maximum #{max} references allowed" if references.length > max
|
|
143
|
+
|
|
144
|
+
references.each_with_index { |reference, index| validate_reference!(reference, index) }
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Validates a single reference entry.
|
|
148
|
+
#
|
|
149
|
+
# @param reference [Hash] Reference entry with exactly one of +data+ or +ref+
|
|
150
|
+
# @param index [Integer] Position in the references array (for error messages)
|
|
151
|
+
# @raise [ValidationError] if the entry is not a Hash, has both or
|
|
152
|
+
# neither of +data+/+ref+, or has a non-String value
|
|
153
|
+
# @api private
|
|
154
|
+
def validate_reference!(reference, index)
|
|
155
|
+
unless reference.is_a?(Hash)
|
|
156
|
+
raise ValidationError, "Reference at index #{index} must be a Hash with exactly one of 'data' or 'ref'"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
data = reference[:data] || reference["data"]
|
|
160
|
+
ref = reference[:ref] || reference["ref"]
|
|
161
|
+
|
|
162
|
+
if data.nil? == ref.nil?
|
|
163
|
+
raise ValidationError, "Reference at index #{index} must include exactly one of 'data' or 'ref'"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
if data.nil?
|
|
167
|
+
validate_reference_value!(ref, "ref", index)
|
|
168
|
+
else
|
|
169
|
+
validate_reference_value!(data, "data", index)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Validates a reference +data+ or +ref+ value.
|
|
174
|
+
#
|
|
175
|
+
# @param value [Object] The data or ref value
|
|
176
|
+
# @param key [String] Which key the value came from ("data" or "ref")
|
|
177
|
+
# @param index [Integer] Position in the references array
|
|
178
|
+
# @raise [ValidationError] if value is not a non-empty String
|
|
179
|
+
# @api private
|
|
180
|
+
def validate_reference_value!(value, key, index)
|
|
181
|
+
return if value.is_a?(String) && !value.empty?
|
|
182
|
+
|
|
183
|
+
raise ValidationError, "Reference at index #{index} '#{key}' must be a non-empty String"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|