Module: RubyLLM::Protocols::ElevenLabs::Flows::Images

Defined in:
lib/ruby_llm/protocols/elevenlabs/flows/images.rb

Overview

:nodoc: all

Constant Summary collapse

MASK_MODELS =
%w[gpt-image-1 gpt-image-1.5 gpt-image-2].freeze

Instance Method Summary collapse

Instance Method Details

#image_aspect_ratio(size) ⇒ Object

Raises:

  • (ArgumentError)


62
63
64
65
66
67
68
69
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 62

def image_aspect_ratio(size)
  match = size.to_s.match(/\A([1-9]\d*)x([1-9]\d*)\z/)
  raise ArgumentError, 'size must be widthxheight or auto' unless match

  width, height = match.captures.map(&:to_i)
  divisor = width.gcd(height)
  "#{width / divisor}:#{height / divisor}"
end

#images_urlObject



10
11
12
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 10

def images_url(**)
  'v1/flows/image'
end

#parse_image_response(response, model:) ⇒ Object



31
32
33
34
35
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 31

def parse_image_response(response, model:)
  id = response.body.fetch('id')
  body = wait_for_image(id)
  Image.new(url: body.fetch('content_url'), mime_type: body.fetch('content_mime_type'), model:)
end

#post_image(payload) ⇒ Object



14
15
16
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 14

def post_image(payload, **)
  @connection.post images_url, payload, usage: @usage_tracker, idempotent: false
end

#render_image_payload(prompt, model:, size:, with: nil, mask: nil, provider_options: {}) ⇒ Object



18
19
20
21
22
23
24
25
26
27
28
29
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 18

def render_image_payload(prompt, model:, size:, with: nil, mask: nil, provider_options: {}, **)
  images = Attachment.wrap(with, config: @config)
  payload = { model_id: model, prompt: }
  payload[:images] = images.map { |image| render_media_reference(image) } if images.any?
  payload[:aspect_ratio] = image_aspect_ratio(size) if size && size != 'auto'
  if mask
    raise ArgumentError, 'ElevenLabs image masks require a GPT Image model' unless MASK_MODELS.include?(model)

    payload[:mask] = render_media_reference(Attachment.wrap(mask, config: @config).first)
  end
  Support::Utils.deep_merge(payload, provider_options)
end

#validate_paint_inputs!(with:, mask:) ⇒ Object

Raises:

  • (ArgumentError)


52
53
54
55
56
57
58
59
60
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 52

def validate_paint_inputs!(with:, mask:)
  images = Attachment.wrap(with, config: @config)
  raise ArgumentError, 'An image mask requires a source image' if mask && images.empty?

  images += Attachment.wrap(mask, config: @config) if mask
  images.each do |image|
    raise UnsupportedAttachmentError, image.mime_type unless image.image?
  end
end

#wait_for_image(id) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/ruby_llm/protocols/elevenlabs/flows/images.rb', line 37

def wait_for_image(id)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + @config.request_timeout
  loop do
    body = @connection.get("#{images_url}/#{id}").body
    state = parse_generation_status(body)
    return body if state[:status] == :completed
    raise Error, "ElevenLabs image generation failed: #{state[:error]}" if state[:status] == :failed
    if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline
      raise Error, "ElevenLabs image generation timed out: #{id}"
    end

    sleep 1
  end
end