diff --git a/app/controllers/basil_controller.rb b/app/controllers/basil_controller.rb index a61c0e73..3e946fe9 100644 --- a/app/controllers/basil_controller.rb +++ b/app/controllers/basil_controller.rb @@ -22,11 +22,11 @@ class BasilController < ApplicationController entity_type: 'Character' ) - gender_field = @character.overview_field('Gender') - @gender_value = Attribute.find_by(attribute_field_id: gender_field.id, entity: @character).try(:value) + @gender_field = @character.overview_field('Gender') + @gender_value = Attribute.find_by(attribute_field_id: @gender_field.id, entity: @character).try(:value) - age_field = @character.overview_field('Age') - @age_value = Attribute.find_by(attribute_field_id: age_field.id, entity: @character).try(:value) + @age_field = @character.overview_field('Age') + @age_value = Attribute.find_by(attribute_field_id: @age_field.id, entity: @character).try(:value) @commissions = BasilCommission.where(entity_type: 'Character', entity_id: @character.id).order('id DESC') @can_request_another = @commissions.all? { |c| c.complete? } @@ -44,42 +44,97 @@ class BasilController < ApplicationController @recent_commissions = BasilCommission.all.includes(:entity, :user).order('id DESC').limit(500) end - def commission_character + def commission + # TODO: when we support multiple page types, we'll want to grab params[:entity_type] and params[:entity_id] + # and constantize the former, then find the entity from the user's content @character = current_user.characters.find(params[:id]) - # Build the prompt - category_ids = AttributeCategory.where( - user_id: current_user.id, entity_type: 'character', label: ['Looks', 'Appearance'] - ).pluck(:id) + # Build the prompt from the character's attributes + category_ids = AttributeCategory.where(user: current_user, entity_type: 'character', label: ['Looks', 'Appearance']) + .pluck(:id) appearance_fields = AttributeField.where(attribute_category_id: category_ids) - attributes = Attribute.where( - attribute_field_id: appearance_fields.pluck(:id), - entity_id: @character.id, - entity_type: 'Character' - ) + attributes = Attribute.where(attribute_field_id: appearance_fields.pluck(:id), + entity_id: @character.id, + entity_type: 'Character') + prompt_components = [] + + # Step 1. Gender gender_field = @character.overview_field('Gender') gender_value = Attribute.find_by(attribute_field_id: gender_field.id, entity: @character).try(:value) + if gender_value.present? + gender_importance = params.dig(:field, gender_field.id.to_s) + + # Add 1 because we present weight to the user as -1 to 1, but it's really 0 to 2 for Stable Diffusion. + if gender_importance.present? + gender_importance = gender_importance.to_f + 1 + end + if gender_importance == 1 + # If the importance is exactly 1, we can omit the parentheses and save a few tokens, since the + # default attention importance is 1. + prompt_components.push gender_value + elsif gender_importance != 0 + # We also want to skip adding gender to the prompt at all if the user marked it as completely unimportant (-1 + 1 = 0) + prompt_components.push "(#{gender_value}:#{gender_importance})" + end + end + + # Step 2. Age age_field = @character.overview_field('Age') age_value = Attribute.find_by(attribute_field_id: age_field.id, entity: @character).try(:value) - if age_value.present? && age_value.to_i.to_s == age_value - age_value = "#{age_value} years old" + if age_value.present? + # If the user simply entered a number in for an age field, we want to help SD along by + # giving it some context. Otherwise, we'll just use the value as-is. + if age_value.to_i.to_s == age_value + age_value = "#{age_value} years old" + end + + age_importance = params.dig(:field, age_field.id.to_s) + + # Add 1 because we present weight to the user as -1 to 1, but it's really 0 to 2 for Stable Diffusion. + if age_importance.present? + age_importance = age_importance.to_f + 1 + end + + if age_importance == 1 + prompt_components.push age_value + elsif age_importance != 0 + # We also want to skip adding gender to the prompt at all if the user marked it as completely unimportant (-1 + 1 = 0) + prompt_components.push "(#{age_value}:#{age_importance})" + end end + # Step 3. Do it all again for every other field, too formatted_field_values = appearance_fields.map do |field| value = attributes.detect { |a| a.attribute_field_id == field.id }.try(:value) + + # If there is no value to this field (or looks like it doesn't apply), skip it. next if value.nil? || value.blank? || ['none', 'n/a', 'no', '.', '-', ' '].include?(value.try(:downcase)) + + # If the field is something implied like a "Human" answer on "Race", skip it. next if field.label.downcase == 'race' && value.downcase == 'human' - "(#{value.gsub(',', '').gsub("\r", "").gsub("\n", " ")} #{field.label})" + # Get the importance of this field and add 1 to get back to our SD version + importance = params.dig(:field, field.id.to_s) + importance = importance.to_f + 1 if importance.present? + + # If the importance is exactly 1, we can omit the parentheses and save a few tokens, since the + # default attention importance is 1. + if importance == 1 + "#{value.gsub(',', '').gsub("\r", "").gsub("\n", " ")} #{field.label}" + elsif importance != 0 + # We also want to skip adding gender to the prompt at all if the user marked it as completely unimportant (-1 + 1 = 0) + "(#{value.gsub(',', '').gsub("\r", "").gsub("\n", " ")} #{field.label}:#{importance})" + else + # For 0-importance fields, we'll compact them out of the list in a moment + nil + end end - prompt = [ - gender_value, - age_value, - formatted_field_values.compact.join(', '), - ].compact.join(', ') + + prompt_components.concat formatted_field_values.compact + prompt = prompt_components.join(', ') BasilCommission.create!( user: current_user, diff --git a/app/services/basil_service.rb b/app/services/basil_service.rb new file mode 100644 index 00000000..260c5e09 --- /dev/null +++ b/app/services/basil_service.rb @@ -0,0 +1,3 @@ +class BasilService < Service + # TODO +end \ No newline at end of file diff --git a/app/views/basil/character.html.erb b/app/views/basil/character.html.erb index 3200bb2b..126526ac 100644 --- a/app/views/basil/character.html.erb +++ b/app/views/basil/character.html.erb @@ -1,180 +1,212 @@ -
-
-
- <%= image_tag @character.random_image_including_private(format: :medium), style: 'width: 100%' %> -

<%= @character.name %>

-
+<%= form_for BasilCommission.new, url: basil_character_path(@character) do |f| %> + <%= f.hidden_field :style, value: 'realistic' %> + <%= f.hidden_field :entity_type, value: 'Character' %> + <%= f.hidden_field :entity_id, value: @character.id %> - + + <% if !shown_any_value %> +
+ Basil works best with guidance! Please fill out some fields in the "Looks" category for this character + before requesting an image. +
+ <% end %> + +
+ <%= link_to 'Customize per-field importance', "javascript:var sliders = document.getElementsByClassName('js-importance-slider'); for(var i = 0; i < sliders.length; i++) sliders.item(i).classList.remove('hide')" %> + +
+ How to customize per-field importance +

+ + This allows you to tell Basil which fields are more or less important to you. For example, if Basil isn't + getting a character's hair color right, you can increase the importance of the "Hair Color" field. +

+ You can also tell Basil to ignore a field entirely by dragging the slider all the way to the left. +
+
+

+
+ <%= link_to 'Edit this character page', edit_polymorphic_path(@character), class: 'grey-text text-darken-2' %> +
+
+ <%= link_to 'Back to my other characters', basil_path, class: 'grey-text text-darken-2' %> +
+
+ +
+
+

+ Basil is learning as fast as he can. Here are some tips and guidelines to get the most out of + him right now: +

+

+ This is still a work in progress and very much a beta that will change a lot before releasing publicly, + but feel free to use it as much as you'd like to provide feedback! +

+

+ If you run into any issues, please let me know <%# in the %> + <%# link_to 'Site Support forums', 'https://www.notebook.ai/forum/site-support' %> + <%# or %> <%= link_to 'on Discord', 'https://discord.gg/7WCuGxY3AW' %>. +

+
+ + <% if @can_request_another && shown_any_value %> +
+ To request Basil create an image of <%= @character.name %>, choose your desired style. +
+
+
+ <%= link_to 'javascript:commission_basil("realistic")' do %> +
+ photo + Photograph +
+ <% end %> +
+
+ <%= link_to 'javascript:commission_basil("painting")' do %> +
+ palette + Painting +
+ <% end %> +
+
+ <%= link_to 'javascript:commission_basil("sketch")' do %> +
+ edit + Sketch +
+ <% end %> +
+
+ <%= link_to 'javascript:commission_basil("digital")' do %> +
+ brush + Digital art +
+ <% end %> +
+
+ <%= link_to 'javascript:commission_basil("anime")' do %> +
+ face + Anime +
+ <% end %> +
+
+ <%= link_to 'javascript:commission_basil("abstract")' do %> +
+ supervised_user_circle + Abstract +
+ <% end %> +
+
+ + + <% end %> + + <% @commissions.each do |commission| %> +
+ <% if commission.complete? %> + <%# image_tag commission.image, style: 'width: 100%' %> + <% + s3 = Aws::S3::Resource.new(region: "us-east-1") + obj = s3.bucket("basil-characters").object("job-#{commission.job_id}.png") + %> +
+
+ <%= link_to obj.presigned_url(:get) do %> + <%= image_tag obj.presigned_url(:get) %> + <% end %> +
+
+
+ <%= @character.name %> +
    +
  • + Completed <%= time_ago_in_words commission.completed_at %> ago +
  • +
  • + Took <%= distance_of_time_in_words commission.completed_at - commission.created_at %> +
  • +
    +
  • + You can click on the image to download it. Congratulations, it's yours now! + Feel free to upload it to your character's page if you want to keep and/or use it. +
  • +
+
-
- <% else %> -
- Basil is still working on this commission... -
- (Requested <%= time_ago_in_words(commission.created_at) %> ago) + <% else %> +
+ Basil is still working on this commission... +
+ (Requested <%= time_ago_in_words(commission.created_at) %> ago) +
-
- <% end %> -
- <% end %> + <% end %> +
+ <% end %> +
- +<% end %> + \ No newline at end of file diff --git a/app/views/basil/review.html.erb b/app/views/basil/review.html.erb index 412b7c00..8596a241 100644 --- a/app/views/basil/review.html.erb +++ b/app/views/basil/review.html.erb @@ -15,6 +15,7 @@
+ <%= commission.id %>. <%= link_to commission.entity.name, commission.entity %> <% if commission.style? %> (<%= commission.style.humanize %>) diff --git a/config/routes.rb b/config/routes.rb index cd173db7..e2a8305e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -5,9 +5,12 @@ Rails.application.routes.draw do scope :ai, path: '/ai' do scope :basil do get '/', to: 'basil#index', as: :basil + get '/character/:id', to: 'basil#character', as: :basil_character - get '/character/:id/commission', to: 'basil#commission_character', as: :basil_commission_character + post '/character/:id', to: 'basil#commission' + #get '/character/:id/commission', to: 'basil#commission_character', as: :basil_commission_character + # TODO this should also be a POST get '/complete/:jobid', to: 'basil#complete_commission' get '/info', to: 'basil#info'