roseflow-stabilityai 0.1.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.
Files changed (33) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.standard.yml +3 -0
  4. data/CHANGELOG.md +4 -0
  5. data/CODE_OF_CONDUCT.md +7 -0
  6. data/Gemfile +13 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +47 -0
  9. data/Rakefile +10 -0
  10. data/lib/roseflow/ai/models/stabilityai_adapter.rb +42 -0
  11. data/lib/roseflow/stabilityai/client.rb +78 -0
  12. data/lib/roseflow/stabilityai/config.rb +18 -0
  13. data/lib/roseflow/stabilityai/model.rb +39 -0
  14. data/lib/roseflow/stabilityai/model_repository.rb +32 -0
  15. data/lib/roseflow/stabilityai/operation_handler.rb +36 -0
  16. data/lib/roseflow/stabilityai/operations/base.rb +25 -0
  17. data/lib/roseflow/stabilityai/operations/image_to_image.rb +50 -0
  18. data/lib/roseflow/stabilityai/operations/masking.rb +54 -0
  19. data/lib/roseflow/stabilityai/operations/text_to_image.rb +37 -0
  20. data/lib/roseflow/stabilityai/operations/upscale.rb +77 -0
  21. data/lib/roseflow/stabilityai/provider.rb +27 -0
  22. data/lib/roseflow/stabilityai/response_handler.rb +57 -0
  23. data/lib/roseflow/stabilityai/responses/base_response.rb +17 -0
  24. data/lib/roseflow/stabilityai/responses/error_response.rb +24 -0
  25. data/lib/roseflow/stabilityai/responses/image_to_image_response.rb +21 -0
  26. data/lib/roseflow/stabilityai/responses/masking_response.rb +21 -0
  27. data/lib/roseflow/stabilityai/responses/text_to_image_response.rb +21 -0
  28. data/lib/roseflow/stabilityai/responses/upscale_response.rb +21 -0
  29. data/lib/roseflow/stabilityai/version.rb +18 -0
  30. data/lib/roseflow/stabilityai.rb +16 -0
  31. data/roseflow-stabilityai.gemspec +46 -0
  32. data/sig/roseflow/stabilityai.rbs +6 -0
  33. metadata +259 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 52d0eb1ead98627dee65b61a5af00bb0f705ef83b1744c7db3e144f2649a16ed
4
+ data.tar.gz: eab68fcb891590911d69d3fb3e2f1ec12f9bb0ae36f3b196d7ec9527b68f9fed
5
+ SHA512:
6
+ metadata.gz: 85894910204e152bb7ed3cd3179777b7e9e6d5a604a53fca7692893b1a3f4bb5048efeb094173b496a0a8be196a63bfc220edcabf030364ceba1493d8a8cd01a
7
+ data.tar.gz: e124b655082293c97b034512023e2061189cdcd9f90a5686b4a2fcbc22a27ded8f2da1862615d5cb4b81b845c22ffaea0a730b6ea9c5332cedd21ae3a40c1d42
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.standard.yml ADDED
@@ -0,0 +1,3 @@
1
+ # For available configuration options, see:
2
+ # https://github.com/testdouble/standard
3
+ ruby_version: 2.6
data/CHANGELOG.md ADDED
@@ -0,0 +1,4 @@
1
+ ## [0.1.0] - 2023-08-09
2
+
3
+ - Initial release
4
+ - Implements text-to-image, image-to-image, SDX4 upscaling, and masking
@@ -0,0 +1,7 @@
1
+ # Contributor Code of Conduct
2
+
3
+ The Roseflow team is committed to fostering a welcoming community.
4
+
5
+ **Our Code of Conduct can be found here**:
6
+
7
+ https://roseflow.ai/conduct
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in roseflow-stabilityai.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rspec", "~> 3.0"
10
+ gem "standard", "~> 1.3"
11
+ gem "ulid-ruby"
12
+
13
+ eval_gemfile "Gemfile.local" if File.exist?("Gemfile.local")
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Lauri Jutila
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Roseflow Stability-AI integration
2
+
3
+ This gem adds Stability-AI API support and integration for Roseflow, a framework for interacting with AI in Ruby.
4
+
5
+ ## Prerequisites
6
+
7
+ To use this gem effectively, you need the [core Roseflow gem](https://github.com/roseflow-ai/roseflow).
8
+
9
+ ## Installation
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ $ bundle add roseflow-stabilityai
14
+
15
+ If bundler is not being used to manage dependencies, install the gem by executing:
16
+
17
+ $ gem install roseflow-stabilityai
18
+
19
+ ## Usage
20
+
21
+ See full documentation how to configure and use Roseflow with StabilityAI at [docs.roseflow.ai](https://docs.roseflow.ai/stabilityai).
22
+
23
+ ## Contributing
24
+
25
+ Bug reports and pull requests are welcome on GitHub at https://github.com/roseflow-ai/roseflow-stabilityai.
26
+
27
+ ## Community
28
+
29
+ ### Discord
30
+
31
+ Join us in our [Discord](https://discord.gg/roseflow).
32
+
33
+ ### Twitter
34
+
35
+ Connect with the core team on Twitter.
36
+
37
+ <a href="https://twitter.com/ljuti" target="_blank">
38
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/ljuti?logo=twitter&style=social">
39
+ </a>
40
+
41
+ ## License
42
+
43
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
44
+
45
+ ## Code of Conduct
46
+
47
+ Everyone interacting in the Roseflow OpenAI project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/roseflow-ai/roseflow-stabilityai/blob/main/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "standard/rake"
9
+
10
+ task default: %i[spec standard]
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/ai/model_interface"
4
+ require "roseflow/ai/models/base_adapter"
5
+
6
+ module Roseflow
7
+ module AI
8
+ module Models
9
+ class StabilityAIAdapter < BaseAdapter
10
+ include ModelInterface
11
+
12
+ def configuration
13
+ @configuration ||= StabilityAI::Model::Configuration.new(engine_id: @model.name)
14
+ end
15
+
16
+ def call(operation, options, &block)
17
+ @model.call(operation, options, &block)
18
+ end
19
+
20
+ def text_to_image(options = {})
21
+ @model.call(:text_to_image, options)
22
+ end
23
+
24
+ def image_to_image(options = {})
25
+ @model.call(:image_to_image, options)
26
+ end
27
+
28
+ def upscale(options = {})
29
+ @model.call(:upscale, options)
30
+ end
31
+
32
+ def masking(options = {})
33
+ @model.call(:masking, options)
34
+ end
35
+
36
+ def operations
37
+ @model.operations
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "faraday/multipart"
5
+ require "faraday/retry"
6
+ require "faraday/typhoeus"
7
+ require "roseflow/types"
8
+ require "roseflow/stabilityai/config"
9
+ require "roseflow/stabilityai/model"
10
+
11
+ module Roseflow
12
+ module StabilityAI
13
+ class Client
14
+ FARADAY_RETRY_OPTIONS = {
15
+ max: 3,
16
+ interval: 0.05,
17
+ interval_randomness: 0.5,
18
+ backoff_factor: 2,
19
+ }
20
+
21
+ def initialize(config = Config.new, provider = nil)
22
+ @config = config
23
+ @provider = provider
24
+ end
25
+
26
+ def models
27
+ response = connection.get("/v1/engines/list")
28
+ body = JSON.parse(response.body)
29
+ end
30
+
31
+ def post(operation)
32
+ if operation.multipart?
33
+ response = multipart_connection.post(operation.path) do |request|
34
+ request.body = operation.body
35
+ end
36
+ else
37
+ response = connection.post(operation.path) do |request|
38
+ request.body = operation.body
39
+ end
40
+ end
41
+ response
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :config, :provider
47
+
48
+ def connection
49
+ @connection ||= Faraday.new(
50
+ url: config.api_url,
51
+ headers: {
52
+ "Stability-Client-ID" => config.client_id,
53
+ "Stability-Client-Version" => config.client_version,
54
+ },
55
+ ) do |faraday|
56
+ faraday.request :authorization, "Bearer", -> { config.api_key }
57
+ faraday.request :json
58
+ faraday.request :retry, FARADAY_RETRY_OPTIONS
59
+ faraday.adapter :typhoeus
60
+ end
61
+ end
62
+
63
+ def multipart_connection
64
+ @multipart_connection ||= Faraday.new(
65
+ url: config.api_url,
66
+ headers: {
67
+ "Stability-Client-ID" => config.client_id,
68
+ "Stability-Client-Version" => config.client_version,
69
+ },
70
+ ) do |faraday|
71
+ faraday.request :authorization, "Bearer", -> { config.api_key }
72
+ faraday.request :multipart
73
+ faraday.adapter :typhoeus
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "anyway_config"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ class Config < Anyway::Config
8
+ config_name :stabilityai
9
+
10
+ attr_config :api_key
11
+ attr_config api_url: "https://api.stability.ai"
12
+ attr_config client_id: "Roseflow Stability-AI Ruby Client"
13
+ attr_config client_version: "0.1.0"
14
+
15
+ required :api_key
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/operation_handler"
4
+ require "roseflow/stabilityai/response_handler"
5
+
6
+ module Roseflow
7
+ module StabilityAI
8
+ class Model
9
+ attr_reader :name
10
+
11
+ def initialize(model_config, provider = nil)
12
+ @model_config = model_config
13
+ @provider = provider
14
+ assign_attributes
15
+ end
16
+
17
+ def call(operation, options)
18
+ operation = OperationHandler.new(operation, options).call
19
+ ResponseHandler.new(operation, client.post(operation)).call
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :model_config
25
+ attr_reader :provider
26
+
27
+ def client
28
+ @client ||= Client.new(provider.send(:config), provider)
29
+ end
30
+
31
+ def assign_attributes
32
+ @name = model_config.fetch("id")
33
+ @description = model_config.fetch("description")
34
+ @descriptive_name = model_config.fetch("name")
35
+ @type = model_config.fetch("type")
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/delegation"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ class ModelRepository
8
+ attr_reader :models
9
+
10
+ delegate :each, :all, :first, :last, to: :models
11
+
12
+ def initialize(provider)
13
+ @provider = provider
14
+ @models = load_models
15
+ end
16
+
17
+ def find(name)
18
+ @models.select { |model| model.name == name }.first
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :provider
24
+
25
+ def load_models
26
+ provider.client.models.map do |model|
27
+ Model.new(model, provider)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "operations/text_to_image"
4
+ require_relative "operations/image_to_image"
5
+ require_relative "operations/upscale"
6
+ require_relative "operations/masking"
7
+
8
+ module Roseflow
9
+ module StabilityAI
10
+ class OperationHandler
11
+ OPERATION_CLASSES = {
12
+ text_to_image: Operations::TextToImage,
13
+ image_to_image: Operations::ImageToImage,
14
+ upscale: Operations::Upscale,
15
+ masking: Operations::Masking,
16
+ }
17
+
18
+ def initialize(operation, options = {})
19
+ @operation = operation
20
+ @options = options
21
+ end
22
+
23
+ def call
24
+ operation_class.new(@options)
25
+ end
26
+
27
+ private
28
+
29
+ def operation_class
30
+ OPERATION_CLASSES.fetch(@operation) do
31
+ raise ArgumentError, "Invalid operation: #{@operation}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module StabilityAI
5
+ module Operations
6
+ class Base < Dry::Struct
7
+ transform_keys(&:to_sym)
8
+
9
+ attribute :engine_id, Types::String
10
+
11
+ def excluded_keys
12
+ [:path]
13
+ end
14
+
15
+ def multipart?
16
+ true
17
+ end
18
+
19
+ def body
20
+ to_h.except(*excluded_keys)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/operations/base"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Operations
8
+ class ImageToImage < Base
9
+ attribute :text_prompts, Types::Array do
10
+ attribute :text, Types::String
11
+ attribute :weight, Types::Float.default(1.0)
12
+ end
13
+ attribute :init_image, Types::String
14
+ attribute :init_image_mode, Types::String.default("IMAGE_STRENGTH")
15
+ attribute :image_strength, Types::Float.default(0.35)
16
+ attribute :cfg_scale, Types::Integer.default(7)
17
+ attribute :clip_guidance_preset, Types::String.default("NONE")
18
+ attribute? :sampler, Types::String
19
+ attribute :samples, Types::Integer.default(1)
20
+ attribute :seed, Types::Integer.default(0)
21
+ attribute :style_preset, Types::String.default("photographic")
22
+
23
+ def path
24
+ "/v1/generation/#{engine_id}/image-to-image"
25
+ end
26
+
27
+ def type
28
+ :image_to_image
29
+ end
30
+
31
+ def excluded_keys
32
+ [:path, :engine_id, :text_prompts]
33
+ end
34
+
35
+ def body
36
+ initial = to_h.except(*excluded_keys)
37
+ initial.merge(
38
+ init_image: Faraday::Multipart::ParamPart.new(
39
+ init_image,
40
+ "image/png",
41
+ ),
42
+ text_prompts: text_prompts.each_with_index.map do |item, index|
43
+ [index, item.to_h]
44
+ end.to_h,
45
+ )
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/operations/base"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Operations
8
+ class Masking < Base
9
+ attribute :text_prompts, Types::Array do
10
+ attribute :text, Types::String
11
+ attribute :weight, Types::Float.default(1.0)
12
+ end
13
+ attribute :init_image, Types::String
14
+ attribute :mask_image, Types::String
15
+ attribute :mask_source, Types::String.default("INIT_IMAGE_ALPHA")
16
+ attribute :cfg_scale, Types::Integer.default(7)
17
+ attribute :clip_guidance_preset, Types::String.default("NONE")
18
+ attribute? :sampler, Types::String
19
+ attribute :samples, Types::Integer.default(1)
20
+ attribute :seed, Types::Integer.default(0)
21
+ attribute :steps, Types::Integer.default(50)
22
+ attribute :style_preset, Types::String.default("photographic")
23
+
24
+ def path
25
+ "/v1/generation/#{engine_id}/image-to-image/masking"
26
+ end
27
+
28
+ def excluded_keys
29
+ [:path, :engine_id, :text_prompts, :mask_image]
30
+ end
31
+
32
+ def body
33
+ to_h.except(*excluded_keys).merge(
34
+ init_image: Faraday::Multipart::ParamPart.new(
35
+ init_image,
36
+ "image/png",
37
+ ),
38
+ # mask_image: Faraday::Multipart::ParamPart.new(
39
+ # mask_image,
40
+ # "image/png",
41
+ # ),
42
+ text_prompts: text_prompts.each_with_index.map do |item, index|
43
+ [index, item.to_h]
44
+ end.to_h,
45
+ )
46
+ end
47
+
48
+ def type
49
+ :masking
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Operations
8
+ class TextToImage < Base
9
+ attribute :height, Types::Integer.default(512)
10
+ attribute :width, Types::Integer.default(512)
11
+ attribute :text_prompts, Types::Array do
12
+ attribute :text, Types::String
13
+ attribute :weight, Types::Float.default(1.0)
14
+ end
15
+ attribute :cfg_scale, Types::Integer.default(7)
16
+ attribute :clip_guidance_preset, Types::String.default("NONE")
17
+ attribute? :sampler, Types::String
18
+ attribute :samples, Types::Integer.default(1)
19
+ attribute :seed, Types::Integer.default(0)
20
+ attribute :steps, Types::Integer.default(50)
21
+ attribute? :style_preset, Types::String.default("photographic")
22
+
23
+ def type
24
+ :text_to_image
25
+ end
26
+
27
+ def multipart?
28
+ false
29
+ end
30
+
31
+ def path
32
+ "/v1/generation/#{engine_id}/text-to-image"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/operations/base"
4
+
5
+ # TODO: Very sharp operation class currently, needs to handle edge cases better.
6
+ module Roseflow
7
+ module StabilityAI
8
+ module Operations
9
+ class Upscale < Base
10
+ attribute :upscale_type, Types::String
11
+
12
+ attribute? :esrgan do
13
+ attribute :image, Types::String
14
+ attribute? :width, Types::Integer
15
+ attribute? :height, Types::Integer
16
+ end
17
+
18
+ attribute? :sdx4 do
19
+ attribute :image, Types::String
20
+ attribute? :width, Types::Integer
21
+ attribute? :height, Types::Integer
22
+ attribute :text_prompts, Types::Array do
23
+ attribute :text, Types::String
24
+ attribute :weight, Types::Float.default(1.0)
25
+ end
26
+ attribute :seed, Types::Integer.default(0)
27
+ attribute :steps, Types::Integer.default(50)
28
+ attribute :cfg_scale, Types::Integer.default(7)
29
+ end
30
+
31
+ def path
32
+ "/v1/generation/#{engine_id}/image-to-image/upscale"
33
+ end
34
+
35
+ def excluded_keys
36
+ [:path, :engine_id, :text_prompts, :upscale_type, :esrgan, :sdx4]
37
+ end
38
+
39
+ def body
40
+ case upscale_type.to_sym
41
+ when :sdx4
42
+ sdx4_body
43
+ when :esrgan
44
+ esrgan_body
45
+ end
46
+ end
47
+
48
+ def sdx4_body
49
+ initial = to_h.except(*excluded_keys)
50
+ sdx4.to_h.merge(
51
+ image: Faraday::Multipart::ParamPart.new(
52
+ sdx4.image,
53
+ "image/png",
54
+ ),
55
+ text_prompts: sdx4.text_prompts.each_with_index.map do |item, index|
56
+ [index, item.to_h]
57
+ end.to_h,
58
+ ).merge(initial)
59
+ end
60
+
61
+ def esrgan_body
62
+ initial = to_h.except(*excluded_keys)
63
+ esrgan.to_h.merge(
64
+ image: Faraday::Multipart::ParamPart.new(
65
+ esrgan.image,
66
+ "image/png",
67
+ ),
68
+ ).merge(initial)
69
+ end
70
+
71
+ def type
72
+ :upscale
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module StabilityAI
5
+ class Provider
6
+ def initialize(config = Config.new)
7
+ @config = config
8
+ end
9
+
10
+ def models
11
+ @models ||= ModelRepository.new(self)
12
+ end
13
+
14
+ def client
15
+ @client ||= Client.new(config, self)
16
+ end
17
+
18
+ def call(operation, options, &block)
19
+ models.find(options.fetch(:engine_id)).call(operation, options, &block)
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :config
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/responses/text_to_image_response"
4
+ require "roseflow/stabilityai/responses/image_to_image_response"
5
+ require "roseflow/stabilityai/responses/upscale_response"
6
+ require "roseflow/stabilityai/responses/masking_response"
7
+ require "roseflow/stabilityai/responses/error_response"
8
+
9
+ module Roseflow
10
+ module StabilityAI
11
+ class ResponseHandler
12
+ def initialize(operation, response)
13
+ @operation = operation
14
+ @response = response
15
+ end
16
+
17
+ def call
18
+ if response.success?
19
+ handle_successful_response
20
+ else
21
+ handle_error_response
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :operation, :response
28
+
29
+ def handle_successful_response
30
+ json = JSON.parse(response.body)
31
+
32
+ case operation.type
33
+ when :text_to_image
34
+ Responses::TextToImageResponse.new(json)
35
+ when :image_to_image
36
+ Responses::ImageToImageResponse.new(json)
37
+ when :upscale
38
+ Responses::UpscaleResponse.new(json)
39
+ when :masking
40
+ Responses::MaskingResponse.new(json)
41
+ else
42
+ raise ArgumentError, "Invalid operation: #{operation.type}"
43
+ end
44
+ end
45
+
46
+ def handle_error_response
47
+ puts response.status
48
+ puts JSON.parse(response.body)
49
+ Responses::ErrorResponse.from(
50
+ code: response.status,
51
+ operation: operation.type,
52
+ body: JSON.parse(response.body),
53
+ )
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/struct"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class BaseResponse < Dry::Struct
9
+ transform_keys(&:to_sym)
10
+
11
+ def success?
12
+ true
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/struct"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class ErrorResponse < Dry::Struct
9
+ transform_keys(&:to_sym)
10
+
11
+ attribute :code, Types::Integer
12
+ attribute :operation, Types::String
13
+
14
+ def success?
15
+ false
16
+ end
17
+
18
+ # TODO: Implement this method
19
+ def self.from(code: nil, operation: nil, body: nil)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_response"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class ImageToImageResponse < BaseResponse
9
+ transform_keys { |key| key.to_s.underscore.to_sym }
10
+
11
+ attribute :artifacts, Types::Array do
12
+ attribute :base64, Types::String
13
+ attribute :finish_reason, Types::String
14
+ attribute :seed, Types::Integer
15
+ end
16
+
17
+ alias_method :images, :artifacts
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_response"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class MaskingResponse < BaseResponse
9
+ transform_keys { |key| key.to_s.underscore.to_sym }
10
+
11
+ attribute :artifacts, Types::Array do
12
+ attribute :base64, Types::String
13
+ attribute :finish_reason, Types::String
14
+ attribute :seed, Types::Integer
15
+ end
16
+
17
+ alias_method :images, :artifacts
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_response"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class TextToImageResponse < BaseResponse
9
+ transform_keys { |key| key.to_s.underscore.to_sym }
10
+
11
+ attribute :artifacts, Types::Array do
12
+ attribute :base64, Types::String
13
+ attribute :finish_reason, Types::String
14
+ attribute :seed, Types::Integer
15
+ end
16
+
17
+ alias_method :images, :artifacts
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./base_response"
4
+
5
+ module Roseflow
6
+ module StabilityAI
7
+ module Responses
8
+ class UpscaleResponse < BaseResponse
9
+ transform_keys { |key| key.to_s.underscore.to_sym }
10
+
11
+ attribute :artifacts, Types::Array do
12
+ attribute :base64, Types::String
13
+ attribute :finish_reason, Types::String
14
+ attribute :seed, Types::Integer
15
+ end
16
+
17
+ alias_method :images, :artifacts
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Roseflow
4
+ module StabilityAI
5
+ def self.gem_version
6
+ Gem::Version.new VERSION::STRING
7
+ end
8
+
9
+ module VERSION
10
+ MAJOR = 0
11
+ MINOR = 1
12
+ PATCH = 0
13
+ PRE = nil
14
+
15
+ STRING = [MAJOR, MINOR, PATCH, PRE].compact.join(".")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "roseflow/stabilityai/version"
4
+ require "roseflow/stabilityai/config"
5
+ require "roseflow/stabilityai/client"
6
+ require "roseflow/stabilityai/model_repository"
7
+ require "roseflow/stabilityai/model"
8
+ require "roseflow/stabilityai/operation_handler"
9
+ require "roseflow/stabilityai/provider"
10
+ require "roseflow/stabilityai/response_handler"
11
+
12
+ module Roseflow
13
+ module StabilityAI
14
+ class Error < StandardError; end
15
+ end
16
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/roseflow/stabilityai/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "roseflow-stabilityai"
7
+ spec.version = Roseflow::StabilityAI.gem_version
8
+ spec.authors = ["Lauri Jutila"]
9
+ spec.email = ["ljuti@users.noreply.github.com"]
10
+
11
+ spec.summary = "Roseflow meets StabilityAI"
12
+ spec.description = "StabilityAI integration and models for Roseflow"
13
+ spec.homepage = "https://github.com/roseflow-ai/roseflow-stabilityai"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/roseflow-ai/roseflow-stabilityai"
19
+ spec.metadata["changelog_uri"] = "https://github.com/roseflow-ai/roseflow-stabilityai/blob/main/CHANGELOG.md"
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(__dir__) do
24
+ `git ls-files -z`.split("\x0").reject do |f|
25
+ (File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
26
+ end
27
+ end
28
+ spec.bindir = "exe"
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
+ spec.require_paths = ["lib"]
31
+
32
+ spec.add_dependency "activesupport"
33
+ spec.add_dependency "anyway_config", "~> 2.0"
34
+ spec.add_dependency "dry-struct"
35
+ spec.add_dependency "faraday"
36
+ spec.add_dependency "faraday-retry"
37
+ spec.add_dependency "faraday-typhoeus"
38
+ spec.add_dependency "faraday-multipart"
39
+
40
+ spec.add_development_dependency "awesome_print"
41
+ spec.add_development_dependency "pry"
42
+ spec.add_development_dependency "pry-byebug"
43
+ spec.add_development_dependency "roseflow"
44
+ spec.add_development_dependency "webmock"
45
+ spec.add_development_dependency "vcr"
46
+ end
@@ -0,0 +1,6 @@
1
+ module Roseflow
2
+ module Stabilityai
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,259 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: roseflow-stabilityai
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Lauri Jutila
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2023-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: anyway_config
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-struct
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: faraday-retry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: faraday-typhoeus
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: faraday-multipart
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: awesome_print
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-byebug
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: roseflow
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: vcr
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: StabilityAI integration and models for Roseflow
196
+ email:
197
+ - ljuti@users.noreply.github.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".rspec"
203
+ - ".standard.yml"
204
+ - CHANGELOG.md
205
+ - CODE_OF_CONDUCT.md
206
+ - Gemfile
207
+ - LICENSE.txt
208
+ - README.md
209
+ - Rakefile
210
+ - lib/roseflow/ai/models/stabilityai_adapter.rb
211
+ - lib/roseflow/stabilityai.rb
212
+ - lib/roseflow/stabilityai/client.rb
213
+ - lib/roseflow/stabilityai/config.rb
214
+ - lib/roseflow/stabilityai/model.rb
215
+ - lib/roseflow/stabilityai/model_repository.rb
216
+ - lib/roseflow/stabilityai/operation_handler.rb
217
+ - lib/roseflow/stabilityai/operations/base.rb
218
+ - lib/roseflow/stabilityai/operations/image_to_image.rb
219
+ - lib/roseflow/stabilityai/operations/masking.rb
220
+ - lib/roseflow/stabilityai/operations/text_to_image.rb
221
+ - lib/roseflow/stabilityai/operations/upscale.rb
222
+ - lib/roseflow/stabilityai/provider.rb
223
+ - lib/roseflow/stabilityai/response_handler.rb
224
+ - lib/roseflow/stabilityai/responses/base_response.rb
225
+ - lib/roseflow/stabilityai/responses/error_response.rb
226
+ - lib/roseflow/stabilityai/responses/image_to_image_response.rb
227
+ - lib/roseflow/stabilityai/responses/masking_response.rb
228
+ - lib/roseflow/stabilityai/responses/text_to_image_response.rb
229
+ - lib/roseflow/stabilityai/responses/upscale_response.rb
230
+ - lib/roseflow/stabilityai/version.rb
231
+ - roseflow-stabilityai.gemspec
232
+ - sig/roseflow/stabilityai.rbs
233
+ homepage: https://github.com/roseflow-ai/roseflow-stabilityai
234
+ licenses:
235
+ - MIT
236
+ metadata:
237
+ homepage_uri: https://github.com/roseflow-ai/roseflow-stabilityai
238
+ source_code_uri: https://github.com/roseflow-ai/roseflow-stabilityai
239
+ changelog_uri: https://github.com/roseflow-ai/roseflow-stabilityai/blob/main/CHANGELOG.md
240
+ post_install_message:
241
+ rdoc_options: []
242
+ require_paths:
243
+ - lib
244
+ required_ruby_version: !ruby/object:Gem::Requirement
245
+ requirements:
246
+ - - ">="
247
+ - !ruby/object:Gem::Version
248
+ version: 3.2.0
249
+ required_rubygems_version: !ruby/object:Gem::Requirement
250
+ requirements:
251
+ - - ">="
252
+ - !ruby/object:Gem::Version
253
+ version: '0'
254
+ requirements: []
255
+ rubygems_version: 3.4.1
256
+ signing_key:
257
+ specification_version: 4
258
+ summary: Roseflow meets StabilityAI
259
+ test_files: []