indentlabs/notebook

View on GitHub
app/views/basil/_character_jam.html.erb

Summary

Maintainability
Test Coverage
<!--
  Partial included for all the character fields/etc for the next character vizjam
-->


<!--
<div class="row">
  <div class="col s12 m12">
    <h1 class="text-center" style="font-size: 2rem">
      <i class="material-icons <%= Character.text_color %>"><%= Character.icon %></i>
      Welcome to our Character VizJam!
    </h1>

    <div class="center">
      <%= image_tag 'basil/character-jam.png', style: 'width: 600px;' %>
      <br />
      Come back to this page on June 8th to start visualizing your characters!
    </div>
  </div>
</div>
-->

<%= content_for :full_width_page_header do %>
  <div class="row">
    <div class="col s12 m12 l6">
      <h1 style="font-size: 2rem; padding: 0 1em">
        <i class="material-icons <%= Character.text_color %>"><%= Character.icon %></i>
        Welcome to our Character VizJam!
      </h1>

      <ul class="collapsible" style="margin: 0 2em">
        <li class="active">
          <div class="collapsible-header">
            <i class="material-icons pink-text">palette</i>
            <strong>Visualize your character</strong>
          </div>
          <div class="collapsible-body">
            <%= form_for basil_jam_submit_path do |f| %>
              <div class="input-field">
                <input placeholder="Nameless character" id="name" name="commission[name]" type="text">
                <label for="name">Name your character, then select their traits from the options below.</label>
              </div>

              <!-- Age radio -->
              <div style="margin-bottom: 1em">
                <div x-data="{ selectedTag: '' }">
                  <strong style="margin-right: 1em">Age</strong>
                  <% options = ['Infant', 'Child', 'Teenager', 'Young Adult', 'Adult', 'Old', 'Very Old'] %>
                  <% options.each do |option| %>
                    <label>
                      <input type="radio" name="commission[age]" value="<%= option %>" />
                      <span class="chip">
                        <%= option %>
                      </span>
                    </label>
                  <% end %>
                </div>
              </div>

              <!-- Gender radio -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Gender</strong>
                <% options = ['Male', 'Female', 'Ambiguous', 'Transgender', 'Non-binary', 'Agender', 'Androgenous', 'Genderqueer'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %>" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Build checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Body type</strong>
                <% options = ['Frail', 'Lean', 'Thin', 'Athletic', 'Hourglass', 'Rectangular', 'Muscular', 'Big-boned', 'Petite', 'Round', 'Pear-shaped', 'Curvy', 'Overweight', 'Underweight'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> body" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Hair color checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Hair color</strong>
                <% options = ['Blonde', 'Black', 'Brown', 'Red', 'White', 'Grey', 'Greying', 'Bald', 'Bleached', 'Blue', 'Green', 'Purple', 'Pink', 'Orange', 'Auburn', 'Rainbow'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> hair color" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Hair style checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Hair style</strong>
                <% options = ['Long', 'Medium', 'Short', 'Wavy', 'Straight', 'Curly', 'Afro', 'Bald', 'Balding', 'Bangs', 'Bob cut', 'Bowl cut', 'Bouffant', 'Braided', 'Bun', 'Buzzcut', 'Chignon', 'Combover', 'Cornrows', 'Crewcut', 'Dreadlocks', 'Emo', 'Feathered', 'Flattop', 'Fringe', 'Mop-top', 'Parted', 'Pigtails', 'Pixie', 'Pompadour', 'Ponytail', 'Rat-tail', 'Rocker', 'Slicked back', 'Spiked'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> hair style" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Eye color checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Eye color</strong>
                <% options = ['Amber', 'Blue', 'Brown', 'Topaz', 'Grey', 'Green', 'Hazel', 'Amethyst', 'Indigo', 'Violet', 'Red', 'Black', 'White'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> eye color" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Skin tone checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Skin tone</strong>
                <% options = ['Light', 'Medium', 'Dark', 'Pale', 'Fair', 'Tan', 'White', 'Brown', 'Black', 'Olive', 'Albino', 'Chocolate', 'Grey', 'Green', 'Blue', 'Red', 'Pink', 'Orange', 'Silver', 'Gold', 'Yellow', 'Purple', 'Freckled', 'Speckled'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> skin tone" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Facial hair checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Facial hair</strong>
                <% options = ['Stubble', 'Patchy', 'Beard', 'Chin curtain', 'Chinstrap', 'Fu Manchu', 'Goatee', 'Mustache', 'Handlebar mustache', 'Horseshoe mustache', 'Mutton chops', 'Neckbeard', 'Sideburns', 'Soul patch'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> facial hair" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <!-- Race checkboxes -->
              <div style="margin-bottom: 1em">
                <strong style="margin-right: 1em">Alternate Race</strong>
                <% options = AutocompleteService.for_field_label(content_model: Character, label: 'Race') - ['Human', 'Dark Elf', 'Half-Elf', 'Half-Dwarf', 'Half-Orc'] %>
                <% options.each do |option| %>
                  <label>
                    <input type="checkbox" name="commission[features][]" value="<%= option %> race" />
                    <span class="chip">
                      <%= option %>
                    </span>
                  </label>
                <% end %>
              </div>

              <div class="center">
                <br />
                <%= f.submit 'Visualize this character', class: 'btn white-text pink' %>
              </div>
            <% end %>
          </div>
        </li>
        <li>
          <div class="collapsible-header">
            <i class="material-icons">help</i>
            How do I save my images?
          </div>
          <div class="collapsible-body">
            <p>
              To save any image, simply right click on it (or long-press if you're on mobile) and click "Save as..." to save
              it to your computer.
            </p>
            <p>
              Feel free to upload your images to their character pages on Notebook.ai if you want to show them off in a gallery
              alongside any other information you have about your character!
              <% unless user_signed_in? %>
                <%= link_to 'You can sign up for a free account here.', new_registration_path(User) %>
              <% end %>
            </p>
          </div>
        </li>
        <li>
          <div class="collapsible-header">
            <i class="material-icons">help</i>
            Who can see the images I generate?
          </div>
          <div class="collapsible-body">
            <p>
              All visualizer images are typically private by default when generated from Notebook.ai, but any images generated from this page
              for the VizJam will be public by default (and visible from this page!). The jam is meant to introduce our creatives to
              the new kinds of tools out there available for visualizing your ideas, and making everything public is a great way to
              learn what's possible from each other. If you want to make private images of your characters, you can always use
              <%= link_to "Notebook.ai's standard visualization feature", basil_path %>.
            </p>
            <p>
              Only the most recent 20 generated images are shown on this page, so make sure you save any images you want to keep! After they fall
              off the list, you won't see them again!
            </p>
          </div>
        </li>
        <li>
          <div class="collapsible-header">
            <i class="material-icons">help</i>
            What if I want an option that isn't available?
          </div>
          <div class="collapsible-body">
            <p>
              Come <%= link_to 'join us on Discord', 'https://discord.gg/bDE8g5YRzp' %>
              and request it! I'll be adding more character options throughout the day based on your feedback. :)
            </p>
          </div>
        </li>
        <li>
          <div class="collapsible-header">
            <i class="material-icons">help</i>
            How is this different from the normal visualization features in Notebook.ai?
          </div>
          <div class="collapsible-body">
            <p>
              Here are the big differences:

              <table>
                <th>
                  <td><strong>VizJam</strong></td>
                  <td><strong>Notebook.ai's Visualizer</strong></td>
                </th>
                <tr>
                  <td><strong>Price</strong></td>
                  <td>Free to use</td>
                  <td>Available with Premium ($7-9/mo)</td>
                </tr>
                <tr>
                  <td><strong>Privacy</strong></td>
                  <td>Public</td>
                  <td>Private</td>
                </tr>
                <tr>
                  <td><strong>Available Styles</strong></td>
                  <td>Realistic</td>
                  <td>Realistic & 11 other styles</td>
                </tr>
                <tr>
                  <td><strong>Content</strong></td>
                  <td>Characters only</td>
                  <td><%= BasilService::ENABLED_PAGE_TYPES.map(&:pluralize).to_sentence %></td>
                </tr>
                <tr>
                  <td><strong>Control</strong></td>
                  <td>Simple checkbox options</td>
                  <td>Unlimited, freeform text</td>
                </tr>
              </table>
            </p>
          </div>
        </li>
        <li>
          <div class="collapsible-header">
            <i class="material-icons">help</i>
            How long will the VizJam last?
          </div>
          <div class="collapsible-body">
            <p>
              This VizJam runs from <strong>June 8rd, 2023</strong> to <strong>June 13th, 2023</strong>. You can follow
              <%= link_to '@IndentLabs on Twitter', 'https://www.twitter.com/IndentLabs', target: '_blank' %>
              or
              <%= link_to '@IndentLabs on Medium', 'https://medium.com/indent-labs', target: '_blank' %>
              to know when the next VizJam will be!
            </p>
          </div>
        </li>
      </ul>
    </div>

    <div class="col s12 m12 l6">
      <h2 style="font-size: 1.4rem">
        Recent visualizations <small>(click one to see their traits, refresh for more)</small>
        <span class="right badge red white-text tooltipped" data-tooltip="<%= @total_count %> characters visualized!">
          <%= number_with_delimiter @total_count %>
        </span>
      </h2>

      <div class="row">
        <div class="col s12 cards-container">
          <% @recent_commissions.each do |commission| %>

            <div class="hoverable card" id='card-<%= commission.job_id %>' data-complete="<%= commission.complete? %>">
              <div class="card-image">
                <%= link_to "#details-#{commission.job_id}", class: 'modal-trigger waves-effect waves-light' do %>
                  <% if commission.complete? %>
                    <%= image_tag commission.image, class: 'commission-image' %>
                  <% else %>
                    <%= image_tag image_path("placeholders/loading.gif"), class: 'commission-image', style: 'background: #2196F3' %>
                  <% end %>
                  <span class="card-title" style="background: black; opacity: 0.75; padding: 4px">
                    <%= commission.final_settings&.fetch('name', '').presence || 'No name' %>
                  </span>
                <% end %>
              </div>
            </div>

            <% if commission.completed_at.nil? %>
              <script>
                document.addEventListener('DOMContentLoaded', (event) => {
                  let jobId = '<%= commission.job_id %>';
                  let card = document.getElementById(`card-${jobId}`);
                  let modal = document.getElementById(`details-${jobId}`);
                  let complete = card.getAttribute('data-complete') === 'true';

                  if (!complete) {
                    console.log('job id ' + jobId + ' is not complete, queueing polling');
                    let interval = setInterval(() => {
                      console.log('polling for', jobId);
                      fetch('<%= basil_commission_info_path(commission.job_id) %>')
                        .then(response => {
                          if(!response.ok) {
                            throw new Error("HTTP error " + response.status);
                          }
                          return response.json();
                        })
                        .then(data => {
                          if (data.completed_at) {
                            console.log('job id ' + jobId + ' is complete, updating image');

                            complete = true;
                            card.setAttribute('data-complete', 'true');

                            cardImage = card.querySelector('.commission-image');
                            cardImage.src = data.image_url;

                            modalImage = modal.querySelector('.commission-image');
                            modalImage.src = data.image_url;
                            clearInterval(interval);
                          } else {
                            console.log('job id ' + jobId + ' is not complete, continuing polling');
                          }
                        })
                        .catch(error => {
                          console.log("Fetch error: " + error);
                        });
                    }, 5000);
                  }
                });
              </script>
            <% end %>

            <div id="details-<%= commission.job_id %>" class="modal modal-fixed-footer">
              <div class="modal-content">
                <h4>
                  <i class="material-icons <%= Character.text_color %>"><%= Character.icon %></i>
                  <%= commission.final_settings&.fetch('name', '').presence || 'Nameless character' %>
                </h4>
                <div class="row">
                  <div class="col s12 m6">
                    <% if commission.complete? %>
                      <%= link_to commission.image, target: '_blank' do %>
                        <%= image_tag commission.image, class: 'commission-image', style: 'width: 100%' %>
                      <% end %>
                      <div class="text-center" style="font-size: 0.8em">
                        Click the image to see it full-size and/or download it.
                      </div>
                    <% else %>
                      <%= image_tag image_path("placeholders/loading.gif"), class: 'commission-image', style: 'width: 100%' %>
                      <div class="text-center" style="font-size: 0.8em">
                        This image is still generating...
                      </div>
                    <% end %>
                  </div>
                  <div class="col s12 m6">
                    <ul style="margin: 0 8px">
                      <li>
                        <strong class="grey-text">Traits:</strong>
                        <div>
                          <% commission.prompt.split(',').map do |tag| %>
                            <span class="red white-text" style="padding: 1.5px 10px; white-space: nowrap;"><%= tag.strip %></span>
                          <% end %>
                        </div>
                        <%# link_to 'Select these traits in my form', '#' %>
                      </li>
                      <li style="padding-top: 1em">
                        <strong class="grey-text">Generated AI Prompt:</strong>
                        <div class="card-panel blue lighten-5">
                          The raw AI parameters that were used are listed below. You can use these in other image generation tools.
                          If they are blank, you may need to refresh the page to see them.
                        </div>
                        <div style="font-size: 0.8em">
                          Sampler: <strong><%= commission.final_settings.fetch('sampler', '') %></strong>
                        </div>
                        <div style="font-size: 0.8em">
                          Steps: <strong><%= commission.final_settings.fetch('steps', '') %></strong>
                        </div>
                        <div style="font-size: 0.8em">
                          CFG scale: <strong><%= commission.final_settings.fetch('cfg_scale', '') %></strong>
                        </div>
                        <div style="font-size: 0.8em">
                          Face restoration: <strong><%= commission.final_settings.fetch('face_restoration_model', '') %></strong>
                        </div>
                        <div>
                          <div style="font-size: 0.8em">Positive prompt:</span>
                          <blockquote style="margin: 5px 0">
                            <%= commission.final_settings.fetch('prompt', '').gsub('ANAD2', 'person') %>
                          </blockquote>
                        </div>
                        <div> 
                          <div style="font-size: 0.8em">Negative prompt:</span>
                          <blockquote style="margin: 5px 0">
                            <%= commission.final_settings.fetch('negative_prompt', '') %>
                          </blockquote>
                        </div>
                        <div style="font-size: 0.8em">
                          Notebook.ai style: <strong><code><%= commission.style %></code></strong>
                        </div>
                      </li>
                      <li>
                        <small class="grey-text">Generation ID: <%= commission.job_id %></small>
                      </li>
                    </ul>
                  </div>
                </div>
              </div>
              <div class="modal-footer">
                <a href="#!" class="modal-close waves-effect waves-green btn-flat">Close</a>
              </div>
            </div>
          <% end %>
        </div>
      </div>

    </div>
  </div>
<% end %>


<script>
function pollingData() {
  return {
    image: 'images/sample-1.jpg',
    jobId: '123', // Job id should be dynamic
    pollingInterval: null,
    init() {
      this.pollingInterval = setInterval(this.poll.bind(this), 5000); // Poll every 5 seconds
    },
    poll() {
      fetch(`/poll/${this.jobId}`)
        .then(response => {
          if (!response.ok) {
            throw new Error('Network response was not ok');
          }
          return response.json();
        })
        .then(data => {
          if (data.image) {
            this.image = data.image;
            clearInterval(this.pollingInterval); // Stop polling if image is returned
          }
        })
        .catch(error => {
          console.error('Error:', error);
        });
    },
  }
}
</script>