ruby_llm-agents 0.5.0 → 1.0.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/README.md +189 -31
- data/app/controllers/ruby_llm/agents/agents_controller.rb +136 -16
- data/app/controllers/ruby_llm/agents/dashboard_controller.rb +29 -9
- data/app/controllers/ruby_llm/agents/workflows_controller.rb +355 -0
- data/app/helpers/ruby_llm/agents/application_helper.rb +25 -0
- data/app/models/ruby_llm/agents/execution.rb +3 -0
- data/app/models/ruby_llm/agents/tenant_budget.rb +58 -15
- data/app/services/ruby_llm/agents/agent_registry.rb +51 -12
- data/app/views/layouts/ruby_llm/agents/application.html.erb +2 -29
- data/app/views/ruby_llm/agents/agents/_agent.html.erb +13 -1
- data/app/views/ruby_llm/agents/agents/_config_agent.html.erb +235 -0
- data/app/views/ruby_llm/agents/agents/_config_embedder.html.erb +70 -0
- data/app/views/ruby_llm/agents/agents/_config_image_generator.html.erb +152 -0
- data/app/views/ruby_llm/agents/agents/_config_moderator.html.erb +63 -0
- data/app/views/ruby_llm/agents/agents/_config_speaker.html.erb +108 -0
- data/app/views/ruby_llm/agents/agents/_config_transcriber.html.erb +91 -0
- data/app/views/ruby_llm/agents/agents/_workflow.html.erb +1 -1
- data/app/views/ruby_llm/agents/agents/index.html.erb +74 -9
- data/app/views/ruby_llm/agents/agents/show.html.erb +18 -378
- data/app/views/ruby_llm/agents/dashboard/_agent_comparison.html.erb +269 -15
- data/app/views/ruby_llm/agents/executions/show.html.erb +16 -0
- data/app/views/ruby_llm/agents/shared/_agent_type_badge.html.erb +93 -0
- data/app/views/ruby_llm/agents/workflows/_step_performance.html.erb +236 -0
- data/app/views/ruby_llm/agents/workflows/_structure_parallel.html.erb +76 -0
- data/app/views/ruby_llm/agents/workflows/_structure_pipeline.html.erb +74 -0
- data/app/views/ruby_llm/agents/workflows/_structure_router.html.erb +108 -0
- data/app/views/ruby_llm/agents/workflows/show.html.erb +442 -0
- data/config/routes.rb +1 -0
- data/lib/generators/ruby_llm_agents/agent_generator.rb +56 -7
- data/lib/generators/ruby_llm_agents/background_remover_generator.rb +110 -0
- data/lib/generators/ruby_llm_agents/embedder_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/image_analyzer_generator.rb +115 -0
- data/lib/generators/ruby_llm_agents/image_editor_generator.rb +108 -0
- data/lib/generators/ruby_llm_agents/image_generator_generator.rb +116 -0
- data/lib/generators/ruby_llm_agents/image_pipeline_generator.rb +178 -0
- data/lib/generators/ruby_llm_agents/image_transformer_generator.rb +109 -0
- data/lib/generators/ruby_llm_agents/image_upscaler_generator.rb +103 -0
- data/lib/generators/ruby_llm_agents/image_variator_generator.rb +102 -0
- data/lib/generators/ruby_llm_agents/install_generator.rb +76 -4
- data/lib/generators/ruby_llm_agents/restructure_generator.rb +292 -0
- data/lib/generators/ruby_llm_agents/speaker_generator.rb +121 -0
- data/lib/generators/ruby_llm_agents/templates/add_execution_type_migration.rb.tt +8 -0
- data/lib/generators/ruby_llm_agents/templates/agent.rb.tt +99 -84
- data/lib/generators/ruby_llm_agents/templates/application_agent.rb.tt +42 -40
- data/lib/generators/ruby_llm_agents/templates/application_background_remover.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_embedder.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_analyzer.rb.tt +26 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_editor.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_generator.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_pipeline.rb.tt +139 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_transformer.rb.tt +21 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_upscaler.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_image_variator.rb.tt +20 -0
- data/lib/generators/ruby_llm_agents/templates/application_speaker.rb.tt +49 -0
- data/lib/generators/ruby_llm_agents/templates/application_transcriber.rb.tt +53 -0
- data/lib/generators/ruby_llm_agents/templates/background_remover.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/embedder.rb.tt +41 -0
- data/lib/generators/ruby_llm_agents/templates/image_analyzer.rb.tt +45 -0
- data/lib/generators/ruby_llm_agents/templates/image_editor.rb.tt +35 -0
- data/lib/generators/ruby_llm_agents/templates/image_generator.rb.tt +47 -0
- data/lib/generators/ruby_llm_agents/templates/image_pipeline.rb.tt +50 -0
- data/lib/generators/ruby_llm_agents/templates/image_transformer.rb.tt +44 -0
- data/lib/generators/ruby_llm_agents/templates/image_upscaler.rb.tt +38 -0
- data/lib/generators/ruby_llm_agents/templates/image_variator.rb.tt +33 -0
- data/lib/generators/ruby_llm_agents/templates/skills/AGENTS.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/BACKGROUND_REMOVERS.md.tt +131 -0
- data/lib/generators/ruby_llm_agents/templates/skills/EMBEDDERS.md.tt +255 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_ANALYZERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_EDITORS.md.tt +102 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_GENERATORS.md.tt +282 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_PIPELINES.md.tt +228 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_TRANSFORMERS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_UPSCALERS.md.tt +110 -0
- data/lib/generators/ruby_llm_agents/templates/skills/IMAGE_VARIATORS.md.tt +120 -0
- data/lib/generators/ruby_llm_agents/templates/skills/SPEAKERS.md.tt +212 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TOOLS.md.tt +227 -0
- data/lib/generators/ruby_llm_agents/templates/skills/TRANSCRIBERS.md.tt +251 -0
- data/lib/generators/ruby_llm_agents/templates/skills/WORKFLOWS.md.tt +300 -0
- data/lib/generators/ruby_llm_agents/templates/speaker.rb.tt +56 -0
- data/lib/generators/ruby_llm_agents/templates/transcriber.rb.tt +51 -0
- data/lib/generators/ruby_llm_agents/transcriber_generator.rb +107 -0
- data/lib/generators/ruby_llm_agents/upgrade_generator.rb +152 -1
- data/lib/ruby_llm/agents/audio/speaker.rb +553 -0
- data/lib/ruby_llm/agents/audio/transcriber.rb +669 -0
- data/lib/ruby_llm/agents/base_agent.rb +675 -0
- data/lib/ruby_llm/agents/core/base/moderation_dsl.rb +181 -0
- data/lib/ruby_llm/agents/core/base/moderation_execution.rb +274 -0
- data/lib/ruby_llm/agents/core/base.rb +135 -0
- data/lib/ruby_llm/agents/core/configuration.rb +981 -0
- data/lib/ruby_llm/agents/core/errors.rb +150 -0
- data/lib/ruby_llm/agents/{instrumentation.rb → core/instrumentation.rb} +22 -1
- data/lib/ruby_llm/agents/core/llm_tenant.rb +358 -0
- data/lib/ruby_llm/agents/{version.rb → core/version.rb} +1 -1
- data/lib/ruby_llm/agents/dsl/base.rb +110 -0
- data/lib/ruby_llm/agents/dsl/caching.rb +142 -0
- data/lib/ruby_llm/agents/dsl/reliability.rb +307 -0
- data/lib/ruby_llm/agents/dsl.rb +41 -0
- data/lib/ruby_llm/agents/image/analyzer/dsl.rb +130 -0
- data/lib/ruby_llm/agents/image/analyzer/execution.rb +402 -0
- data/lib/ruby_llm/agents/image/analyzer.rb +90 -0
- data/lib/ruby_llm/agents/image/background_remover/dsl.rb +154 -0
- data/lib/ruby_llm/agents/image/background_remover/execution.rb +240 -0
- data/lib/ruby_llm/agents/image/background_remover.rb +89 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_dsl.rb +91 -0
- data/lib/ruby_llm/agents/image/concerns/image_operation_execution.rb +165 -0
- data/lib/ruby_llm/agents/image/editor/dsl.rb +56 -0
- data/lib/ruby_llm/agents/image/editor/execution.rb +207 -0
- data/lib/ruby_llm/agents/image/editor.rb +92 -0
- data/lib/ruby_llm/agents/image/generator/active_storage_support.rb +127 -0
- data/lib/ruby_llm/agents/image/generator/content_policy.rb +95 -0
- data/lib/ruby_llm/agents/image/generator/pricing.rb +353 -0
- data/lib/ruby_llm/agents/image/generator/templates.rb +124 -0
- data/lib/ruby_llm/agents/image/generator.rb +455 -0
- data/lib/ruby_llm/agents/image/pipeline/dsl.rb +213 -0
- data/lib/ruby_llm/agents/image/pipeline/execution.rb +382 -0
- data/lib/ruby_llm/agents/image/pipeline.rb +97 -0
- data/lib/ruby_llm/agents/image/transformer/dsl.rb +148 -0
- data/lib/ruby_llm/agents/image/transformer/execution.rb +223 -0
- data/lib/ruby_llm/agents/image/transformer.rb +95 -0
- data/lib/ruby_llm/agents/image/upscaler/dsl.rb +83 -0
- data/lib/ruby_llm/agents/image/upscaler/execution.rb +219 -0
- data/lib/ruby_llm/agents/image/upscaler.rb +81 -0
- data/lib/ruby_llm/agents/image/variator/dsl.rb +62 -0
- data/lib/ruby_llm/agents/image/variator/execution.rb +189 -0
- data/lib/ruby_llm/agents/image/variator.rb +80 -0
- data/lib/ruby_llm/agents/{alert_manager.rb → infrastructure/alert_manager.rb} +17 -22
- data/lib/ruby_llm/agents/infrastructure/budget/budget_query.rb +145 -0
- data/lib/ruby_llm/agents/infrastructure/budget/config_resolver.rb +149 -0
- data/lib/ruby_llm/agents/infrastructure/budget/forecaster.rb +68 -0
- data/lib/ruby_llm/agents/infrastructure/budget/spend_recorder.rb +279 -0
- data/lib/ruby_llm/agents/infrastructure/budget_tracker.rb +275 -0
- data/lib/ruby_llm/agents/{execution_logger_job.rb → infrastructure/execution_logger_job.rb} +17 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/executor.rb +2 -1
- data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/retry_strategy.rb +9 -3
- data/lib/ruby_llm/agents/{reliability.rb → infrastructure/reliability.rb} +11 -21
- data/lib/ruby_llm/agents/pipeline/builder.rb +215 -0
- data/lib/ruby_llm/agents/pipeline/context.rb +255 -0
- data/lib/ruby_llm/agents/pipeline/executor.rb +86 -0
- data/lib/ruby_llm/agents/pipeline/middleware/base.rb +124 -0
- data/lib/ruby_llm/agents/pipeline/middleware/budget.rb +95 -0
- data/lib/ruby_llm/agents/pipeline/middleware/cache.rb +171 -0
- data/lib/ruby_llm/agents/pipeline/middleware/instrumentation.rb +415 -0
- data/lib/ruby_llm/agents/pipeline/middleware/reliability.rb +276 -0
- data/lib/ruby_llm/agents/pipeline/middleware/tenant.rb +196 -0
- data/lib/ruby_llm/agents/pipeline.rb +68 -0
- data/lib/ruby_llm/agents/{engine.rb → rails/engine.rb} +79 -11
- data/lib/ruby_llm/agents/results/background_removal_result.rb +286 -0
- data/lib/ruby_llm/agents/{result.rb → results/base.rb} +73 -1
- data/lib/ruby_llm/agents/results/embedding_result.rb +243 -0
- data/lib/ruby_llm/agents/results/image_analysis_result.rb +314 -0
- data/lib/ruby_llm/agents/results/image_edit_result.rb +250 -0
- data/lib/ruby_llm/agents/results/image_generation_result.rb +346 -0
- data/lib/ruby_llm/agents/results/image_pipeline_result.rb +399 -0
- data/lib/ruby_llm/agents/results/image_transform_result.rb +251 -0
- data/lib/ruby_llm/agents/results/image_upscale_result.rb +255 -0
- data/lib/ruby_llm/agents/results/image_variation_result.rb +237 -0
- data/lib/ruby_llm/agents/results/moderation_result.rb +158 -0
- data/lib/ruby_llm/agents/results/speech_result.rb +338 -0
- data/lib/ruby_llm/agents/results/transcription_result.rb +408 -0
- data/lib/ruby_llm/agents/text/embedder.rb +444 -0
- data/lib/ruby_llm/agents/text/moderator.rb +237 -0
- data/lib/ruby_llm/agents/workflow/async.rb +220 -0
- data/lib/ruby_llm/agents/workflow/async_executor.rb +156 -0
- data/lib/ruby_llm/agents/{workflow.rb → workflow/orchestrator.rb} +6 -5
- data/lib/ruby_llm/agents/workflow/parallel.rb +34 -17
- data/lib/ruby_llm/agents/workflow/thread_pool.rb +185 -0
- data/lib/ruby_llm/agents.rb +86 -20
- metadata +172 -34
- data/lib/ruby_llm/agents/base/caching.rb +0 -40
- data/lib/ruby_llm/agents/base/cost_calculation.rb +0 -105
- data/lib/ruby_llm/agents/base/dsl.rb +0 -324
- data/lib/ruby_llm/agents/base/execution.rb +0 -366
- data/lib/ruby_llm/agents/base/reliability_dsl.rb +0 -82
- data/lib/ruby_llm/agents/base/reliability_execution.rb +0 -136
- data/lib/ruby_llm/agents/base/response_building.rb +0 -86
- data/lib/ruby_llm/agents/base/tool_tracking.rb +0 -57
- data/lib/ruby_llm/agents/base.rb +0 -210
- data/lib/ruby_llm/agents/budget_tracker.rb +0 -733
- data/lib/ruby_llm/agents/configuration.rb +0 -394
- /data/lib/ruby_llm/agents/{deprecations.rb → core/deprecations.rb} +0 -0
- /data/lib/ruby_llm/agents/{inflections.rb → core/inflections.rb} +0 -0
- /data/lib/ruby_llm/agents/{resolved_config.rb → core/resolved_config.rb} +0 -0
- /data/lib/ruby_llm/agents/{attempt_tracker.rb → infrastructure/attempt_tracker.rb} +0 -0
- /data/lib/ruby_llm/agents/{cache_helper.rb → infrastructure/cache_helper.rb} +0 -0
- /data/lib/ruby_llm/agents/{circuit_breaker.rb → infrastructure/circuit_breaker.rb} +0 -0
- /data/lib/ruby_llm/agents/{redactor.rb → infrastructure/redactor.rb} +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/breaker_manager.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/execution_constraints.rb +0 -0
- /data/lib/ruby_llm/agents/{reliability → infrastructure/reliability}/fallback_routing.rb +0 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require_relative "../concerns/image_operation_execution"
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Agents
|
|
8
|
+
class ImageTransformer
|
|
9
|
+
# Execution logic for image transformers
|
|
10
|
+
#
|
|
11
|
+
# Handles image validation, content policy checks, budget tracking,
|
|
12
|
+
# caching, image transformation, and result building.
|
|
13
|
+
#
|
|
14
|
+
module Execution
|
|
15
|
+
include Concerns::ImageOperationExecution
|
|
16
|
+
|
|
17
|
+
# Execute the image transformation pipeline
|
|
18
|
+
#
|
|
19
|
+
# @return [ImageTransformResult] The result containing transformed image
|
|
20
|
+
def execute
|
|
21
|
+
started_at = Time.current
|
|
22
|
+
|
|
23
|
+
resolve_tenant_context!
|
|
24
|
+
check_budget! if budget_tracking_enabled?
|
|
25
|
+
validate_inputs!
|
|
26
|
+
validate_content_policy!
|
|
27
|
+
|
|
28
|
+
# Check cache
|
|
29
|
+
cached = check_cache(ImageTransformResult) if cache_enabled?
|
|
30
|
+
return cached if cached
|
|
31
|
+
|
|
32
|
+
# Transform image(s)
|
|
33
|
+
transformed_images = transform_images
|
|
34
|
+
|
|
35
|
+
# Build result
|
|
36
|
+
result = build_result(
|
|
37
|
+
images: transformed_images,
|
|
38
|
+
started_at: started_at,
|
|
39
|
+
completed_at: Time.current
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Cache result
|
|
43
|
+
write_cache(result) if cache_enabled?
|
|
44
|
+
|
|
45
|
+
# Track execution
|
|
46
|
+
record_execution(result) if execution_tracking_enabled?
|
|
47
|
+
|
|
48
|
+
result
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
record_failed_execution(e, started_at) if execution_tracking_enabled?
|
|
51
|
+
build_error_result(e, started_at)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def execution_type
|
|
57
|
+
"image_transform"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def validate_inputs!
|
|
61
|
+
raise ArgumentError, "Image cannot be blank" if image.nil?
|
|
62
|
+
raise ArgumentError, "Prompt cannot be blank" if prompt.nil? || prompt.strip.empty?
|
|
63
|
+
|
|
64
|
+
# Validate image exists if it's a path
|
|
65
|
+
if image.is_a?(String) && !image.start_with?("http")
|
|
66
|
+
unless File.exist?(image)
|
|
67
|
+
raise ArgumentError, "Image file does not exist: #{image}"
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Validate prompt length
|
|
72
|
+
max_length = config.max_image_prompt_length || 4000
|
|
73
|
+
if prompt.length > max_length
|
|
74
|
+
raise ArgumentError, "Prompt exceeds maximum length of #{max_length} characters"
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def validate_content_policy!
|
|
79
|
+
policy = self.class.content_policy
|
|
80
|
+
return if policy == :none || policy == :standard
|
|
81
|
+
|
|
82
|
+
ImageGenerator::ContentPolicy.validate!(prompt, policy)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def transform_images
|
|
86
|
+
count = resolve_count
|
|
87
|
+
|
|
88
|
+
Array.new(count) do
|
|
89
|
+
transform_single_image
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def transform_single_image
|
|
94
|
+
final_prompt = apply_template(prompt)
|
|
95
|
+
|
|
96
|
+
# Use img2img/transformation API
|
|
97
|
+
RubyLLM.paint(
|
|
98
|
+
final_prompt,
|
|
99
|
+
model: resolve_model,
|
|
100
|
+
size: resolve_size,
|
|
101
|
+
image: image,
|
|
102
|
+
strength: resolve_strength,
|
|
103
|
+
**build_transform_options
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def apply_template(text)
|
|
108
|
+
template = self.class.template_string
|
|
109
|
+
return text unless template
|
|
110
|
+
|
|
111
|
+
template.gsub("{prompt}", text)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def build_transform_options
|
|
115
|
+
opts = {}
|
|
116
|
+
opts[:negative_prompt] = resolve_negative_prompt if resolve_negative_prompt
|
|
117
|
+
opts[:guidance_scale] = resolve_guidance_scale if resolve_guidance_scale
|
|
118
|
+
opts[:steps] = resolve_steps if resolve_steps
|
|
119
|
+
opts[:preserve_composition] = resolve_preserve_composition
|
|
120
|
+
opts[:assume_model_exists] = true if options[:assume_model_exists]
|
|
121
|
+
opts
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def build_result(images:, started_at:, completed_at:)
|
|
125
|
+
ImageTransformResult.new(
|
|
126
|
+
images: images,
|
|
127
|
+
source_image: image,
|
|
128
|
+
prompt: prompt,
|
|
129
|
+
model_id: resolve_model,
|
|
130
|
+
size: resolve_size,
|
|
131
|
+
strength: resolve_strength,
|
|
132
|
+
started_at: started_at,
|
|
133
|
+
completed_at: completed_at,
|
|
134
|
+
tenant_id: @tenant_id,
|
|
135
|
+
transformer_class: self.class.name
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_error_result(error, started_at)
|
|
140
|
+
ImageTransformResult.new(
|
|
141
|
+
images: [],
|
|
142
|
+
source_image: image,
|
|
143
|
+
prompt: prompt,
|
|
144
|
+
model_id: resolve_model,
|
|
145
|
+
size: resolve_size,
|
|
146
|
+
strength: resolve_strength,
|
|
147
|
+
started_at: started_at,
|
|
148
|
+
completed_at: Time.current,
|
|
149
|
+
tenant_id: @tenant_id,
|
|
150
|
+
transformer_class: self.class.name,
|
|
151
|
+
error_class: error.class.name,
|
|
152
|
+
error_message: error.message
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Resolution methods
|
|
157
|
+
|
|
158
|
+
def resolve_size
|
|
159
|
+
options[:size] || self.class.size
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def resolve_strength
|
|
163
|
+
options[:strength] || self.class.strength
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def resolve_preserve_composition
|
|
167
|
+
options.fetch(:preserve_composition, self.class.preserve_composition)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def resolve_negative_prompt
|
|
171
|
+
options[:negative_prompt] || self.class.negative_prompt
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def resolve_guidance_scale
|
|
175
|
+
options[:guidance_scale] || self.class.guidance_scale
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def resolve_steps
|
|
179
|
+
options[:steps] || self.class.steps
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def resolve_count
|
|
183
|
+
options[:count] || 1
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Cache key components
|
|
187
|
+
def cache_key_components
|
|
188
|
+
[
|
|
189
|
+
"image_transformer",
|
|
190
|
+
self.class.name,
|
|
191
|
+
self.class.version,
|
|
192
|
+
resolve_model,
|
|
193
|
+
resolve_size,
|
|
194
|
+
resolve_strength.to_s,
|
|
195
|
+
Digest::SHA256.hexdigest(prompt),
|
|
196
|
+
Digest::SHA256.hexdigest(image_digest)
|
|
197
|
+
]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def image_digest
|
|
201
|
+
if image.is_a?(String) && File.exist?(image)
|
|
202
|
+
File.read(image)
|
|
203
|
+
elsif image.respond_to?(:read)
|
|
204
|
+
content = image.read
|
|
205
|
+
image.rewind if image.respond_to?(:rewind)
|
|
206
|
+
content
|
|
207
|
+
else
|
|
208
|
+
image.to_s
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def build_execution_metadata(result)
|
|
213
|
+
{
|
|
214
|
+
count: result.count,
|
|
215
|
+
size: result.size,
|
|
216
|
+
strength: result.strength,
|
|
217
|
+
prompt_length: prompt.length
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "transformer/dsl"
|
|
4
|
+
require_relative "transformer/execution"
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Agents
|
|
8
|
+
# Image transformer for style transfer and image-to-image generation
|
|
9
|
+
#
|
|
10
|
+
# Transforms an existing image based on a text prompt while
|
|
11
|
+
# maintaining the overall structure. The strength parameter
|
|
12
|
+
# controls how much the image is transformed.
|
|
13
|
+
#
|
|
14
|
+
# @example Basic usage
|
|
15
|
+
# result = RubyLLM::Agents::ImageTransformer.call(
|
|
16
|
+
# image: "path/to/photo.jpg",
|
|
17
|
+
# prompt: "Convert to watercolor painting style"
|
|
18
|
+
# )
|
|
19
|
+
# result.url # => "https://..."
|
|
20
|
+
#
|
|
21
|
+
# @example Custom transformer class
|
|
22
|
+
# class AnimeTransformer < RubyLLM::Agents::ImageTransformer
|
|
23
|
+
# model "sdxl"
|
|
24
|
+
# strength 0.8
|
|
25
|
+
# template "anime style, studio ghibli, {prompt}"
|
|
26
|
+
#
|
|
27
|
+
# description "Transforms photos into anime style"
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# result = AnimeTransformer.call(
|
|
31
|
+
# image: user_photo,
|
|
32
|
+
# prompt: "portrait of a person"
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
35
|
+
class ImageTransformer
|
|
36
|
+
extend DSL
|
|
37
|
+
include Execution
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
# Execute image transformation
|
|
41
|
+
#
|
|
42
|
+
# @param image [String, IO] Path, URL, or IO object of the source image
|
|
43
|
+
# @param prompt [String] Description of the desired transformation
|
|
44
|
+
# @param options [Hash] Additional options (model, strength, size, etc.)
|
|
45
|
+
# @return [ImageTransformResult] The result containing transformed image
|
|
46
|
+
def call(image:, prompt:, **options)
|
|
47
|
+
new(image: image, prompt: prompt, **options).call
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Ensure subclasses inherit DSL settings
|
|
51
|
+
def inherited(subclass)
|
|
52
|
+
super
|
|
53
|
+
subclass.instance_variable_set(:@model, @model)
|
|
54
|
+
subclass.instance_variable_set(:@size, @size)
|
|
55
|
+
subclass.instance_variable_set(:@strength, @strength)
|
|
56
|
+
subclass.instance_variable_set(:@preserve_composition, @preserve_composition)
|
|
57
|
+
subclass.instance_variable_set(:@version, @version)
|
|
58
|
+
subclass.instance_variable_set(:@description, @description)
|
|
59
|
+
subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
|
|
60
|
+
subclass.instance_variable_set(:@content_policy, @content_policy)
|
|
61
|
+
subclass.instance_variable_set(:@template_string, @template_string)
|
|
62
|
+
subclass.instance_variable_set(:@negative_prompt, @negative_prompt)
|
|
63
|
+
subclass.instance_variable_set(:@guidance_scale, @guidance_scale)
|
|
64
|
+
subclass.instance_variable_set(:@steps, @steps)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :image, :prompt, :options, :tenant_id
|
|
69
|
+
|
|
70
|
+
# Initialize a new image transformer instance
|
|
71
|
+
#
|
|
72
|
+
# @param image [String, IO] Source image (path, URL, or IO object)
|
|
73
|
+
# @param prompt [String] Description of the desired transformation
|
|
74
|
+
# @param options [Hash] Additional options
|
|
75
|
+
# @option options [String] :model Model to use
|
|
76
|
+
# @option options [String] :size Output image size
|
|
77
|
+
# @option options [Float] :strength Transformation strength (0.0-1.0)
|
|
78
|
+
# @option options [Integer] :count Number of transformations to generate
|
|
79
|
+
# @option options [Object] :tenant Tenant for multi-tenancy
|
|
80
|
+
def initialize(image:, prompt:, **options)
|
|
81
|
+
@image = image
|
|
82
|
+
@prompt = prompt
|
|
83
|
+
@options = options
|
|
84
|
+
@tenant_id = nil
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Execute the image transformation
|
|
88
|
+
#
|
|
89
|
+
# @return [ImageTransformResult] The result containing transformed image
|
|
90
|
+
def call
|
|
91
|
+
execute
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../concerns/image_operation_dsl"
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Agents
|
|
7
|
+
class ImageUpscaler
|
|
8
|
+
# DSL for configuring image upscalers
|
|
9
|
+
#
|
|
10
|
+
# Provides class-level methods to configure model, scale factor,
|
|
11
|
+
# and other upscaling parameters.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# class PhotoUpscaler < RubyLLM::Agents::ImageUpscaler
|
|
15
|
+
# model "real-esrgan"
|
|
16
|
+
# scale 4
|
|
17
|
+
# face_enhance true
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
module DSL
|
|
21
|
+
include Concerns::ImageOperationDSL
|
|
22
|
+
|
|
23
|
+
VALID_SCALES = [2, 4, 8].freeze
|
|
24
|
+
|
|
25
|
+
# Set or get the upscale factor
|
|
26
|
+
#
|
|
27
|
+
# @param value [Integer, nil] Scale factor (2, 4, or 8)
|
|
28
|
+
# @return [Integer] The scale factor
|
|
29
|
+
def scale(value = nil)
|
|
30
|
+
if value
|
|
31
|
+
unless VALID_SCALES.include?(value)
|
|
32
|
+
raise ArgumentError, "Scale must be one of: #{VALID_SCALES.join(', ')}"
|
|
33
|
+
end
|
|
34
|
+
@scale = value
|
|
35
|
+
else
|
|
36
|
+
@scale || inherited_or_default(:scale, 4)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Set or get face enhancement
|
|
41
|
+
#
|
|
42
|
+
# When enabled, applies additional enhancement to detected faces.
|
|
43
|
+
# Uses models like GFPGAN for face restoration.
|
|
44
|
+
#
|
|
45
|
+
# @param value [Boolean, nil] Enable face enhancement
|
|
46
|
+
# @return [Boolean] Whether face enhancement is enabled
|
|
47
|
+
def face_enhance(value = nil)
|
|
48
|
+
if value.nil?
|
|
49
|
+
result = @face_enhance
|
|
50
|
+
result = inherited_or_default(:face_enhance, false) if result.nil?
|
|
51
|
+
result
|
|
52
|
+
else
|
|
53
|
+
@face_enhance = value
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Set or get denoise strength
|
|
58
|
+
#
|
|
59
|
+
# Controls how much noise reduction is applied.
|
|
60
|
+
# Higher values remove more noise but may lose detail.
|
|
61
|
+
#
|
|
62
|
+
# @param value [Float, nil] Denoise strength (0.0-1.0)
|
|
63
|
+
# @return [Float] The denoise strength
|
|
64
|
+
def denoise_strength(value = nil)
|
|
65
|
+
if value
|
|
66
|
+
unless value.is_a?(Numeric) && value.between?(0.0, 1.0)
|
|
67
|
+
raise ArgumentError, "Denoise strength must be between 0.0 and 1.0"
|
|
68
|
+
end
|
|
69
|
+
@denoise_strength = value.to_f
|
|
70
|
+
else
|
|
71
|
+
@denoise_strength || inherited_or_default(:denoise_strength, 0.5)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def default_model
|
|
78
|
+
config.default_upscaler_model || "real-esrgan"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require_relative "../concerns/image_operation_execution"
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Agents
|
|
8
|
+
class ImageUpscaler
|
|
9
|
+
# Execution logic for image upscalers
|
|
10
|
+
#
|
|
11
|
+
# Handles image validation, budget tracking, caching,
|
|
12
|
+
# upscaling execution, and result building.
|
|
13
|
+
#
|
|
14
|
+
module Execution
|
|
15
|
+
include Concerns::ImageOperationExecution
|
|
16
|
+
|
|
17
|
+
# Execute the image upscaling pipeline
|
|
18
|
+
#
|
|
19
|
+
# @return [ImageUpscaleResult] The result containing upscaled image
|
|
20
|
+
def execute
|
|
21
|
+
started_at = Time.current
|
|
22
|
+
|
|
23
|
+
resolve_tenant_context!
|
|
24
|
+
check_budget! if budget_tracking_enabled?
|
|
25
|
+
validate_image!
|
|
26
|
+
|
|
27
|
+
# Check cache
|
|
28
|
+
cached = check_cache(ImageUpscaleResult) if cache_enabled?
|
|
29
|
+
return cached if cached
|
|
30
|
+
|
|
31
|
+
# Upscale image
|
|
32
|
+
upscaled_image = upscale_image
|
|
33
|
+
|
|
34
|
+
# Build result
|
|
35
|
+
result = build_result(
|
|
36
|
+
image: upscaled_image,
|
|
37
|
+
started_at: started_at,
|
|
38
|
+
completed_at: Time.current
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# Cache result
|
|
42
|
+
write_cache(result) if cache_enabled?
|
|
43
|
+
|
|
44
|
+
# Track execution
|
|
45
|
+
record_execution(result) if execution_tracking_enabled?
|
|
46
|
+
|
|
47
|
+
result
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
record_failed_execution(e, started_at) if execution_tracking_enabled?
|
|
50
|
+
build_error_result(e, started_at)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def execution_type
|
|
56
|
+
"image_upscale"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validate_image!
|
|
60
|
+
raise ArgumentError, "Image cannot be blank" if image.nil?
|
|
61
|
+
|
|
62
|
+
# Validate image exists if it's a path
|
|
63
|
+
if image.is_a?(String) && !image.start_with?("http")
|
|
64
|
+
unless File.exist?(image)
|
|
65
|
+
raise ArgumentError, "Image file does not exist: #{image}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def upscale_image
|
|
71
|
+
# Use RubyLLM's upscale endpoint if available
|
|
72
|
+
if RubyLLM.respond_to?(:upscale_image)
|
|
73
|
+
RubyLLM.upscale_image(
|
|
74
|
+
image: image,
|
|
75
|
+
model: resolve_model,
|
|
76
|
+
scale: resolve_scale,
|
|
77
|
+
**build_upscale_options
|
|
78
|
+
)
|
|
79
|
+
else
|
|
80
|
+
# For models that support upscaling through custom endpoints
|
|
81
|
+
# This would typically be handled by a Replicate or custom provider
|
|
82
|
+
upscale_via_provider
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def upscale_via_provider
|
|
87
|
+
# Attempt to use the model through RubyLLM's paint with upscale mode
|
|
88
|
+
# This is a fallback for when direct upscale isn't supported
|
|
89
|
+
RubyLLM.paint(
|
|
90
|
+
"Upscale this image",
|
|
91
|
+
model: resolve_model,
|
|
92
|
+
image: image,
|
|
93
|
+
upscale_factor: resolve_scale,
|
|
94
|
+
face_enhance: resolve_face_enhance,
|
|
95
|
+
denoise_strength: resolve_denoise_strength,
|
|
96
|
+
**build_upscale_options
|
|
97
|
+
)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def build_upscale_options
|
|
101
|
+
opts = {}
|
|
102
|
+
opts[:face_enhance] = resolve_face_enhance if resolve_face_enhance
|
|
103
|
+
opts[:denoise_strength] = resolve_denoise_strength if resolve_denoise_strength
|
|
104
|
+
opts[:assume_model_exists] = true if options[:assume_model_exists]
|
|
105
|
+
opts
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def build_result(image:, started_at:, completed_at:)
|
|
109
|
+
# Calculate output size based on scale
|
|
110
|
+
output_size = calculate_output_size
|
|
111
|
+
|
|
112
|
+
ImageUpscaleResult.new(
|
|
113
|
+
image: image,
|
|
114
|
+
source_image: self.image,
|
|
115
|
+
model_id: resolve_model,
|
|
116
|
+
scale: resolve_scale,
|
|
117
|
+
output_size: output_size,
|
|
118
|
+
face_enhance: resolve_face_enhance,
|
|
119
|
+
started_at: started_at,
|
|
120
|
+
completed_at: completed_at,
|
|
121
|
+
tenant_id: @tenant_id,
|
|
122
|
+
upscaler_class: self.class.name
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def build_error_result(error, started_at)
|
|
127
|
+
ImageUpscaleResult.new(
|
|
128
|
+
image: nil,
|
|
129
|
+
source_image: self.image,
|
|
130
|
+
model_id: resolve_model,
|
|
131
|
+
scale: resolve_scale,
|
|
132
|
+
output_size: nil,
|
|
133
|
+
face_enhance: resolve_face_enhance,
|
|
134
|
+
started_at: started_at,
|
|
135
|
+
completed_at: Time.current,
|
|
136
|
+
tenant_id: @tenant_id,
|
|
137
|
+
upscaler_class: self.class.name,
|
|
138
|
+
error_class: error.class.name,
|
|
139
|
+
error_message: error.message
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def calculate_output_size
|
|
144
|
+
input_size = get_input_image_size
|
|
145
|
+
return nil unless input_size
|
|
146
|
+
|
|
147
|
+
width, height = input_size
|
|
148
|
+
scale = resolve_scale
|
|
149
|
+
"#{width * scale}x#{height * scale}"
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def get_input_image_size
|
|
153
|
+
return nil unless image.is_a?(String) && File.exist?(image)
|
|
154
|
+
|
|
155
|
+
# Try to get image dimensions
|
|
156
|
+
# This requires an image processing library like MiniMagick or ImageMagick
|
|
157
|
+
if defined?(MiniMagick)
|
|
158
|
+
img = MiniMagick::Image.open(image)
|
|
159
|
+
[img.width, img.height]
|
|
160
|
+
elsif defined?(Vips)
|
|
161
|
+
img = Vips::Image.new_from_file(image)
|
|
162
|
+
[img.width, img.height]
|
|
163
|
+
else
|
|
164
|
+
nil
|
|
165
|
+
end
|
|
166
|
+
rescue StandardError
|
|
167
|
+
nil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Resolution methods
|
|
171
|
+
|
|
172
|
+
def resolve_scale
|
|
173
|
+
options[:scale] || self.class.scale
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def resolve_face_enhance
|
|
177
|
+
options.fetch(:face_enhance, self.class.face_enhance)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def resolve_denoise_strength
|
|
181
|
+
options[:denoise_strength] || self.class.denoise_strength
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Cache key components
|
|
185
|
+
def cache_key_components
|
|
186
|
+
[
|
|
187
|
+
"image_upscaler",
|
|
188
|
+
self.class.name,
|
|
189
|
+
self.class.version,
|
|
190
|
+
resolve_model,
|
|
191
|
+
resolve_scale.to_s,
|
|
192
|
+
resolve_face_enhance.to_s,
|
|
193
|
+
Digest::SHA256.hexdigest(image_digest)
|
|
194
|
+
]
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def image_digest
|
|
198
|
+
if image.is_a?(String) && File.exist?(image)
|
|
199
|
+
File.read(image)
|
|
200
|
+
elsif image.respond_to?(:read)
|
|
201
|
+
content = image.read
|
|
202
|
+
image.rewind if image.respond_to?(:rewind)
|
|
203
|
+
content
|
|
204
|
+
else
|
|
205
|
+
image.to_s
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def build_execution_metadata(result)
|
|
210
|
+
{
|
|
211
|
+
scale: result.scale,
|
|
212
|
+
output_size: result.output_size,
|
|
213
|
+
face_enhance: result.face_enhance
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "upscaler/dsl"
|
|
4
|
+
require_relative "upscaler/execution"
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Agents
|
|
8
|
+
# Image upscaler for resolution enhancement
|
|
9
|
+
#
|
|
10
|
+
# Increases the resolution of images using AI upscaling models.
|
|
11
|
+
# Supports 2x, 4x, and 8x upscaling with optional face enhancement.
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage
|
|
14
|
+
# result = RubyLLM::Agents::ImageUpscaler.call(image: "path/to/low_res.jpg")
|
|
15
|
+
# result.url # => "https://..." (high resolution version)
|
|
16
|
+
#
|
|
17
|
+
# @example Custom upscaler class
|
|
18
|
+
# class PhotoUpscaler < RubyLLM::Agents::ImageUpscaler
|
|
19
|
+
# model "real-esrgan"
|
|
20
|
+
# scale 4
|
|
21
|
+
# face_enhance true
|
|
22
|
+
#
|
|
23
|
+
# description "Upscales photos with face enhancement"
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# result = PhotoUpscaler.call(image: portrait_photo)
|
|
27
|
+
# result.size # => "4096x4096" (if input was 1024x1024)
|
|
28
|
+
#
|
|
29
|
+
class ImageUpscaler
|
|
30
|
+
extend DSL
|
|
31
|
+
include Execution
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
# Execute image upscaling
|
|
35
|
+
#
|
|
36
|
+
# @param image [String, IO] Path, URL, or IO object of the source image
|
|
37
|
+
# @param options [Hash] Additional options (model, scale, etc.)
|
|
38
|
+
# @return [ImageUpscaleResult] The result containing upscaled image
|
|
39
|
+
def call(image:, **options)
|
|
40
|
+
new(image: image, **options).call
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Ensure subclasses inherit DSL settings
|
|
44
|
+
def inherited(subclass)
|
|
45
|
+
super
|
|
46
|
+
subclass.instance_variable_set(:@model, @model)
|
|
47
|
+
subclass.instance_variable_set(:@scale, @scale)
|
|
48
|
+
subclass.instance_variable_set(:@face_enhance, @face_enhance)
|
|
49
|
+
subclass.instance_variable_set(:@denoise_strength, @denoise_strength)
|
|
50
|
+
subclass.instance_variable_set(:@version, @version)
|
|
51
|
+
subclass.instance_variable_set(:@description, @description)
|
|
52
|
+
subclass.instance_variable_set(:@cache_ttl, @cache_ttl)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
attr_reader :image, :options, :tenant_id
|
|
57
|
+
|
|
58
|
+
# Initialize a new image upscaler instance
|
|
59
|
+
#
|
|
60
|
+
# @param image [String, IO] Source image (path, URL, or IO object)
|
|
61
|
+
# @param options [Hash] Additional options
|
|
62
|
+
# @option options [String] :model Model to use
|
|
63
|
+
# @option options [Integer] :scale Upscale factor (2, 4, or 8)
|
|
64
|
+
# @option options [Boolean] :face_enhance Enable face enhancement
|
|
65
|
+
# @option options [Float] :denoise_strength Denoising strength (0.0-1.0)
|
|
66
|
+
# @option options [Object] :tenant Tenant for multi-tenancy
|
|
67
|
+
def initialize(image:, **options)
|
|
68
|
+
@image = image
|
|
69
|
+
@options = options
|
|
70
|
+
@tenant_id = nil
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Execute the image upscaling
|
|
74
|
+
#
|
|
75
|
+
# @return [ImageUpscaleResult] The result containing upscaled image
|
|
76
|
+
def call
|
|
77
|
+
execute
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|