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 %>
-
- -
-
- Gender
-
-
- <%= @gender_value %>
-
-
+
+
+
+ <%= image_tag @character.random_image_including_private(format: :medium), style: 'width: 100%' %>
+
<%= @character.name %>
+
-
-
-
- Age
-
-
- <%= @age_value %>
-
-
-
- <% shown_any_value = false %>
- <% @appearance_fields.each do |field| %>
- <%
- value = @attributes.detect { |attr| attr.attribute_field_id == field.id }.try(:value)
- next if value.nil? || value.blank?
- shown_any_value = true
- %>
-
-
+
+ -
- <%= field.label %>
+ Gender
+ <%= range_field_tag "field[#{@gender_field.id}]", 0, { min: -1, max: 1, step: 0.25, style: 'width: 50%', class: 'js-importance-slider hide' } %>
- <%= value %>
+ <%= @gender_value %>
- <% end %>
-
- <% 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 %>
+ -
+
+ Age
+ <%= range_field_tag "field[#{@age_field.id}]", 0, { min: -1, max: 1, step: 0.25, style: 'width: 50%', class: 'js-importance-slider hide' } %>
+
+
+ <%= @age_value %>
+
+
-
- <%= 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 basil_commission_character_path(@character, style: 'realistic') do %>
-
- photo
- Photograph
-
- <% end %>
-
-
- <%= link_to basil_commission_character_path(@character, style: 'painting') do %>
-
- palette
- Painting
-
- <% end %>
-
-
- <%= link_to basil_commission_character_path(@character, style: 'sketch') do %>
-
- edit
- Sketch
-
- <% end %>
-
-
- <%= link_to basil_commission_character_path(@character, style: 'digital') do %>
-
- brush
- Digital art
-
- <% end %>
-
-
- <%= link_to basil_commission_character_path(@character, style: 'anime') do %>
-
- face
- Anime
-
- <% end %>
-
-
- <%= link_to basil_commission_character_path(@character, style: 'abstract') do %>
-
- supervised_user_circle
- Abstract
-
- <% end %>
-
-
-
-
- <% end %>
-
- <% @commissions.each do |commission| %>
-
- <% if commission.complete? %>
- <%# image_tag commission.image, style: 'width: 100%' %>
+ <% shown_any_value = false %>
+ <% @appearance_fields.each do |field| %>
<%
- s3 = Aws::S3::Resource.new(region: "us-east-1")
- obj = s3.bucket("basil-characters").object("job-#{commission.job_id}.png")
+ value = @attributes.detect { |attr| attr.attribute_field_id == field.id }.try(:value)
+ next if value.nil? || value.blank?
+ shown_any_value = true
%>
-
-
- <%= link_to obj.presigned_url(:get) do %>
- <%= image_tag obj.presigned_url(:get) %>
- <% end %>
+
-
+
+ <%= field.label %>
+ <%= range_field_tag "field[#{field.id}]", 0, { min: -1, max: 1, step: 0.25, style: 'width: 50%', class: 'js-importance-slider hide' } %>
-
-
-
<%= @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.
-
-
+
+ <%= value %>
+
+
+ <% end %>
+
+
+ <% 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'