reve_ai 0.1.1 → 0.2.1
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 +30 -0
- data/README.md +275 -7
- 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 +30 -4
|
@@ -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
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../base"
|
|
4
|
+
|
|
5
|
+
module ReveAI
|
|
6
|
+
module Resources
|
|
7
|
+
class V2
|
|
8
|
+
# v2 layout pipeline operations (experimental).
|
|
9
|
+
#
|
|
10
|
+
# The layout endpoints expose lower-level control over image
|
|
11
|
+
# composition: extracting structured layouts from images, generating
|
|
12
|
+
# layouts from prompts, and rendering images from layouts.
|
|
13
|
+
#
|
|
14
|
+
# @note Experimental: these endpoints require care and experimentation
|
|
15
|
+
# to achieve good results. For simple image generation and
|
|
16
|
+
# prompt-based editing, prefer {V2::Images#create}.
|
|
17
|
+
#
|
|
18
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
19
|
+
class Layouts < Base
|
|
20
|
+
# @return [String] API endpoint for layout extraction
|
|
21
|
+
EXTRACT_ENDPOINT = "/v2/image/extract_layout"
|
|
22
|
+
|
|
23
|
+
# @return [String] API endpoint for layout generation
|
|
24
|
+
CREATE_ENDPOINT = "/v2/image/create_layout"
|
|
25
|
+
|
|
26
|
+
# @return [String] API endpoint for layout rendering
|
|
27
|
+
RENDER_ENDPOINT = "/v2/image/render_layout"
|
|
28
|
+
|
|
29
|
+
# Extracts a structured layout from an image.
|
|
30
|
+
#
|
|
31
|
+
# @param image [Hash] Source image: exactly one of +data+ (base64
|
|
32
|
+
# encoded image String) or +ref+ (identifier String: +"id:<uuid>"+
|
|
33
|
+
# or +"reference:@<name>"+)
|
|
34
|
+
# @param prompt [String, nil] Optional instruction for transforming
|
|
35
|
+
# the extracted layout (max 4000 chars)
|
|
36
|
+
# @param version [String, nil] Optional public model version alias
|
|
37
|
+
# @param breadcrumb [String, nil] Request tracking value sent as the
|
|
38
|
+
# +breadcrumb+ query parameter; ignored by the API
|
|
39
|
+
#
|
|
40
|
+
# @return [LayoutResponse] Response containing the extracted +layout+
|
|
41
|
+
#
|
|
42
|
+
# @raise [ValidationError] if image is malformed or prompt exceeds max length
|
|
43
|
+
#
|
|
44
|
+
# @example Extract a layout from an image
|
|
45
|
+
# result = client.v2.layouts.extract(image: { data: photo_base64 })
|
|
46
|
+
# result.layout # => { prompt: "...", regions: [...], width: 4672, height: 3520 }
|
|
47
|
+
#
|
|
48
|
+
# @note Experimental: layout extraction commonly takes 10-40 seconds.
|
|
49
|
+
def extract(image:, prompt: nil, version: nil, breadcrumb: nil)
|
|
50
|
+
validate_raw_image!(image, "Image")
|
|
51
|
+
validate_prompt!(prompt, max_length: Configuration::V2_MAX_PROMPT_LENGTH) if prompt
|
|
52
|
+
|
|
53
|
+
body = { image: image }
|
|
54
|
+
body[:prompt] = prompt if prompt
|
|
55
|
+
body[:version] = version if version
|
|
56
|
+
|
|
57
|
+
response = post(EXTRACT_ENDPOINT, body, params: breadcrumb_params(breadcrumb))
|
|
58
|
+
LayoutResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Generates (or edits) a structured layout without rendering an image.
|
|
62
|
+
#
|
|
63
|
+
# @param prompt [String, nil] Description of the desired layout
|
|
64
|
+
# (max 4000 chars); at least one of +prompt+ or +references+ is required
|
|
65
|
+
# @param references [Array<Hash>, nil] Up to 8 ordered compound
|
|
66
|
+
# references ({Configuration::V2_MAX_REFERENCES}); each entry may
|
|
67
|
+
# contain +image+ (a raw { data: }/{ ref: } image Hash), +layout+
|
|
68
|
+
# (a layout Hash), and/or +prompt+ (String) — at least one per entry
|
|
69
|
+
# @param commands [Array<Hash>, nil] Ordered imperative layout edits;
|
|
70
|
+
# each entry is a Hash with an +op+ key (add, place, shift, remove,
|
|
71
|
+
# keep, change) plus op-specific fields
|
|
72
|
+
# @param aspect_ratio [String, nil] Layout aspect ratio; supported set
|
|
73
|
+
# ({Configuration::ASPECT_RATIOS}), default "auto"
|
|
74
|
+
# @param version [String, nil] Optional public model version alias
|
|
75
|
+
# @param breadcrumb [String, nil] Request tracking value sent as the
|
|
76
|
+
# +breadcrumb+ query parameter; ignored by the API
|
|
77
|
+
#
|
|
78
|
+
# @return [LayoutResponse] Response containing the generated +layout+
|
|
79
|
+
#
|
|
80
|
+
# @raise [ValidationError] if neither prompt nor references is given,
|
|
81
|
+
# or any argument is malformed
|
|
82
|
+
#
|
|
83
|
+
# @example Free-form layout from a prompt
|
|
84
|
+
# result = client.v2.layouts.create(prompt: "a person at a cafe")
|
|
85
|
+
# result.layout[:regions] # => [{ label: "person", bbox: {...}, ... }]
|
|
86
|
+
#
|
|
87
|
+
# @note Experimental: layout generation commonly takes 10-40 seconds.
|
|
88
|
+
def create(prompt: nil, references: nil, commands: nil, aspect_ratio: nil, version: nil, breadcrumb: nil)
|
|
89
|
+
validate_prompt_or_references!(prompt, references)
|
|
90
|
+
validate_prompt!(prompt, max_length: Configuration::V2_MAX_PROMPT_LENGTH) if prompt
|
|
91
|
+
validate_compound_references!(references)
|
|
92
|
+
validate_commands!(commands)
|
|
93
|
+
validate_aspect_ratio!(aspect_ratio, Configuration::ASPECT_RATIOS)
|
|
94
|
+
|
|
95
|
+
body = build_create_body(prompt: prompt, references: references, commands: commands,
|
|
96
|
+
aspect_ratio: aspect_ratio, version: version)
|
|
97
|
+
|
|
98
|
+
response = post(CREATE_ENDPOINT, body, params: breadcrumb_params(breadcrumb))
|
|
99
|
+
LayoutResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Renders a final image from a target layout.
|
|
103
|
+
#
|
|
104
|
+
# @param layout [Hash] The layout to render; must include a non-empty
|
|
105
|
+
# +regions+ Array (each region: +label+, +prompt+, +bbox+ with
|
|
106
|
+
# normalized x0/y0/x1/y1; optional +parent+, +region_type+,
|
|
107
|
+
# +image_index+, +image_region_index+)
|
|
108
|
+
# @param references [Array<Hash>, nil] Up to 8 ordered compound
|
|
109
|
+
# references; same shape as {#create}
|
|
110
|
+
# @param postprocessing [Array<Hash>, nil] Postprocessing steps, each
|
|
111
|
+
# with a +process+ key (e.g., { process: "fit_image", max_dim: 2048 })
|
|
112
|
+
# @param version [String, nil] Optional public model version alias
|
|
113
|
+
# @param accept [String, nil] Per-request Accept header: "image/png",
|
|
114
|
+
# "image/jpeg", or "image/webp" for a binary image response, or
|
|
115
|
+
# "application/json" (default)
|
|
116
|
+
# @param breadcrumb [String, nil] Request tracking value sent as the
|
|
117
|
+
# +breadcrumb+ query parameter; ignored by the API
|
|
118
|
+
#
|
|
119
|
+
# @return [ImageResponse] Response containing the rendered image and
|
|
120
|
+
# the produced +layout+
|
|
121
|
+
#
|
|
122
|
+
# @raise [ValidationError] if layout is missing or malformed
|
|
123
|
+
#
|
|
124
|
+
# @example Render a layout to an image
|
|
125
|
+
# layout = { regions: [{ label: "cat", prompt: "a tabby cat",
|
|
126
|
+
# bbox: { x0: 0.2, y0: 0.2, x1: 0.8, y1: 0.8 } }] }
|
|
127
|
+
# result = client.v2.layouts.render(layout: layout)
|
|
128
|
+
# File.binwrite("cat.png", Base64.decode64(result.image))
|
|
129
|
+
#
|
|
130
|
+
# @note Experimental: rendering commonly takes 40-80 seconds.
|
|
131
|
+
def render(layout:, references: nil, postprocessing: nil, version: nil, accept: nil, breadcrumb: nil)
|
|
132
|
+
validate_layout!(layout)
|
|
133
|
+
validate_compound_references!(references)
|
|
134
|
+
validate_postprocessing!(postprocessing)
|
|
135
|
+
|
|
136
|
+
body = { layout: layout }
|
|
137
|
+
body[:references] = references if references
|
|
138
|
+
body[:postprocessing] = postprocessing if postprocessing
|
|
139
|
+
body[:version] = version if version
|
|
140
|
+
|
|
141
|
+
response = post(RENDER_ENDPOINT, body, params: breadcrumb_params(breadcrumb), accept: accept)
|
|
142
|
+
ImageResponse.new(status: response.status, headers: response.headers, body: response.body)
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
private
|
|
146
|
+
|
|
147
|
+
# Builds the request body for the create_layout endpoint.
|
|
148
|
+
#
|
|
149
|
+
# @return [Hash] Request body with only the provided options
|
|
150
|
+
# @api private
|
|
151
|
+
def build_create_body(prompt:, references:, commands:, aspect_ratio:, version:)
|
|
152
|
+
body = {}
|
|
153
|
+
body[:prompt] = prompt if prompt
|
|
154
|
+
body[:references] = references if references
|
|
155
|
+
body[:commands] = commands if commands
|
|
156
|
+
body[:aspect_ratio] = aspect_ratio if aspect_ratio
|
|
157
|
+
body[:version] = version if version
|
|
158
|
+
body
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Returns the query params Hash for a breadcrumb, or nil.
|
|
162
|
+
#
|
|
163
|
+
# @param breadcrumb [String, nil] Breadcrumb value
|
|
164
|
+
# @return [Hash, nil] Query params
|
|
165
|
+
# @api private
|
|
166
|
+
def breadcrumb_params(breadcrumb)
|
|
167
|
+
breadcrumb ? { breadcrumb: breadcrumb } : nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Reads a Hash value accepting symbol or string keys.
|
|
171
|
+
#
|
|
172
|
+
# @param hash [Hash] The Hash to read
|
|
173
|
+
# @param key [Symbol] The key to look up (symbol or string form)
|
|
174
|
+
# @return [Object, nil] The value, or nil when absent
|
|
175
|
+
# @api private
|
|
176
|
+
def fetch_value(hash, key)
|
|
177
|
+
hash[key] || hash[key.to_s]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Validates that at least one of prompt or references is present.
|
|
181
|
+
#
|
|
182
|
+
# @raise [ValidationError] if both are nil/empty
|
|
183
|
+
# @api private
|
|
184
|
+
def validate_prompt_or_references!(prompt, references)
|
|
185
|
+
return if prompt && !prompt.empty?
|
|
186
|
+
return if references && !references.empty?
|
|
187
|
+
|
|
188
|
+
raise ValidationError, "At least one of prompt or references is required"
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Validates a raw image object ({ data: } or { ref: } shape).
|
|
192
|
+
#
|
|
193
|
+
# @param image [Object] The value to validate
|
|
194
|
+
# @param label [String] Label for error messages
|
|
195
|
+
# @raise [ValidationError] if not a Hash with exactly one non-empty
|
|
196
|
+
# String +data+ or +ref+ value
|
|
197
|
+
# @api private
|
|
198
|
+
def validate_raw_image!(image, label)
|
|
199
|
+
raise ValidationError, "#{label} must be a Hash with exactly one of 'data' or 'ref'" unless image.is_a?(Hash)
|
|
200
|
+
|
|
201
|
+
data = fetch_value(image, :data)
|
|
202
|
+
ref = fetch_value(image, :ref)
|
|
203
|
+
|
|
204
|
+
raise ValidationError, "#{label} must include exactly one of 'data' or 'ref'" if data.nil? == ref.nil?
|
|
205
|
+
|
|
206
|
+
value = data || ref
|
|
207
|
+
return if value.is_a?(String) && !value.empty?
|
|
208
|
+
|
|
209
|
+
raise ValidationError, "#{label} '#{data ? "data" : "ref"}' must be a non-empty String"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Validates compound layout references.
|
|
213
|
+
#
|
|
214
|
+
# @param references [Array<Hash>, nil] Compound reference entries
|
|
215
|
+
# @raise [ValidationError] if not an Array of valid compound entries
|
|
216
|
+
# @api private
|
|
217
|
+
def validate_compound_references!(references)
|
|
218
|
+
return if references.nil?
|
|
219
|
+
|
|
220
|
+
unless references.is_a?(Array)
|
|
221
|
+
raise ValidationError, "References must be an Array of compound reference Hashes"
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
max = Configuration::V2_MAX_REFERENCES
|
|
225
|
+
raise ValidationError, "Maximum #{max} references allowed" if references.length > max
|
|
226
|
+
|
|
227
|
+
references.each_with_index { |reference, index| validate_compound_reference!(reference, index) }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Validates a single compound reference entry.
|
|
231
|
+
#
|
|
232
|
+
# @param reference [Hash] Entry with at least one of image/layout/prompt
|
|
233
|
+
# @param index [Integer] Position in the references array
|
|
234
|
+
# @raise [ValidationError] if the entry carries none of the allowed
|
|
235
|
+
# fields, or carries a malformed image/layout
|
|
236
|
+
# @api private
|
|
237
|
+
def validate_compound_reference!(reference, index)
|
|
238
|
+
raise ValidationError, "Reference at index #{index} must be a Hash" unless reference.is_a?(Hash)
|
|
239
|
+
|
|
240
|
+
image = fetch_value(reference, :image)
|
|
241
|
+
layout = fetch_value(reference, :layout)
|
|
242
|
+
prompt = fetch_value(reference, :prompt)
|
|
243
|
+
|
|
244
|
+
if image.nil? && layout.nil? && prompt.nil?
|
|
245
|
+
raise ValidationError,
|
|
246
|
+
"Reference at index #{index} must include at least one of 'image', 'layout', 'prompt'"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
validate_raw_image!(image, "Reference at index #{index} 'image'") if image
|
|
250
|
+
validate_reference_layout!(layout, index)
|
|
251
|
+
validate_reference_prompt!(prompt, index)
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Validates the layout value inside a compound reference.
|
|
255
|
+
#
|
|
256
|
+
# @param layout [Object] The value to check
|
|
257
|
+
# @param index [Integer] Position in the references array
|
|
258
|
+
# @raise [ValidationError] if present and not a Hash
|
|
259
|
+
# @api private
|
|
260
|
+
def validate_reference_layout!(layout, index)
|
|
261
|
+
return if layout.nil? || layout.is_a?(Hash)
|
|
262
|
+
|
|
263
|
+
raise ValidationError, "Reference at index #{index} 'layout' must be a Hash"
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Validates the prompt value inside a compound reference.
|
|
267
|
+
#
|
|
268
|
+
# @param prompt [Object] The value to check
|
|
269
|
+
# @param index [Integer] Position in the references array
|
|
270
|
+
# @raise [ValidationError] if present and not a non-empty String
|
|
271
|
+
# @api private
|
|
272
|
+
def validate_reference_prompt!(prompt, index)
|
|
273
|
+
return if prompt.nil?
|
|
274
|
+
return if prompt.is_a?(String) && !prompt.empty?
|
|
275
|
+
|
|
276
|
+
raise ValidationError, "Reference at index #{index} 'prompt' must be a non-empty String"
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Validates the target layout for rendering.
|
|
280
|
+
#
|
|
281
|
+
# @param layout [Object] The layout to validate
|
|
282
|
+
# @raise [ValidationError] if not a Hash with a non-empty regions Array
|
|
283
|
+
# @api private
|
|
284
|
+
def validate_layout!(layout)
|
|
285
|
+
regions = fetch_value(layout, :regions) if layout.is_a?(Hash)
|
|
286
|
+
return if regions.is_a?(Array) && !regions.empty?
|
|
287
|
+
|
|
288
|
+
raise ValidationError, "Layout must be a Hash with a non-empty 'regions' Array"
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Validates layout commands.
|
|
292
|
+
#
|
|
293
|
+
# @param commands [Array<Hash>, nil] Command entries
|
|
294
|
+
# @raise [ValidationError] if not an Array of Hashes with an +op+ key
|
|
295
|
+
# @api private
|
|
296
|
+
def validate_commands!(commands)
|
|
297
|
+
return if commands.nil?
|
|
298
|
+
raise ValidationError, "Commands must be an Array of Hashes with an 'op' key" unless commands.is_a?(Array)
|
|
299
|
+
|
|
300
|
+
commands.each_with_index do |command, index|
|
|
301
|
+
next if command.is_a?(Hash) && (command.key?(:op) || command.key?("op"))
|
|
302
|
+
|
|
303
|
+
raise ValidationError, "Command at index #{index} must be a Hash with an 'op' key"
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ReveAI
|
|
4
|
+
module Resources
|
|
5
|
+
# Namespace for Reve API v2 resources.
|
|
6
|
+
#
|
|
7
|
+
# Accessed via `client.v2`. Groups the v2 image create endpoint and the
|
|
8
|
+
# experimental layout pipeline endpoints.
|
|
9
|
+
#
|
|
10
|
+
# @example Generate an image with the v2 API
|
|
11
|
+
# result = client.v2.images.create(prompt: "A sunset over mountains")
|
|
12
|
+
# result.layout # => { prompt: "...", regions: [...] }
|
|
13
|
+
#
|
|
14
|
+
# @see https://api.reve.com/console/docs Reve API Documentation
|
|
15
|
+
class V2
|
|
16
|
+
# @return [Client] The client instance for this namespace
|
|
17
|
+
attr_reader :client
|
|
18
|
+
|
|
19
|
+
# Creates a new v2 namespace.
|
|
20
|
+
#
|
|
21
|
+
# @param client [Client] The API client
|
|
22
|
+
# @api private
|
|
23
|
+
def initialize(client)
|
|
24
|
+
@client = client
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns the v2 images resource.
|
|
28
|
+
#
|
|
29
|
+
# @return [V2::Images] v2 image generation operations
|
|
30
|
+
def images
|
|
31
|
+
@images ||= Images.new(client)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the v2 layouts resource (experimental endpoints).
|
|
35
|
+
#
|
|
36
|
+
# @return [V2::Layouts] v2 layout pipeline operations
|
|
37
|
+
def layouts
|
|
38
|
+
@layouts ||= Layouts.new(client)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|