mirror of
https://github.com/indentlabs/notebook.git
synced 2025-10-26 11:19:22 +00:00
Merge branch 'master' into tailwind-redesign
This commit is contained in:
commit
0ff6d4dffc
50
Dockerfile
50
Dockerfile
@ -5,30 +5,62 @@
|
||||
# manually is the preferred setup for Notebook.ai instances.
|
||||
|
||||
# The image to build from.
|
||||
FROM ruby:2.7.6
|
||||
FROM ruby:3.2.1
|
||||
|
||||
# Properties/labels for the image.
|
||||
LABEL maintainer="Notebook.ai Contributors"
|
||||
|
||||
# Development ENV by default, but overridable with RAILS_ENV
|
||||
ARG RAILS_ENV=development
|
||||
ENV RAILS_ENV=${RAILS_ENV}
|
||||
|
||||
# Copy the current folder into the notebookai user's home directory.
|
||||
COPY . /home/notebookai
|
||||
|
||||
# Set the notebookai user's home directory as the working directory.
|
||||
WORKDIR /home/notebookai
|
||||
|
||||
# Prep the image for runtime, this should all be done in one command
|
||||
# to minimize image layers.
|
||||
# See: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#minimize-the-number-of-layers
|
||||
# Create the notebookai user and group
|
||||
RUN groupadd --system --gid 1000 notebookai && \
|
||||
useradd --system --home-dir /home/notebookai --gid notebookai --uid 1000 --shell /bin/bash notebookai && \
|
||||
useradd --system --home-dir /home/notebookai --gid notebookai --uid 1000 --shell /bin/bash notebookai
|
||||
|
||||
# Install system dependencies
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && \
|
||||
apt-get update -qq && \
|
||||
apt-get install -y build-essential libpq-dev nodejs imagemagick libmagickwand-dev && \
|
||||
rm --recursive --force /var/lib/apt/lists/* && \
|
||||
bundle install && \
|
||||
rails db:setup
|
||||
apt-get install -y build-essential libpq-dev nodejs imagemagick libmagickwand-dev curl && \
|
||||
rm --recursive --force /var/lib/apt/lists/*
|
||||
|
||||
# Install yarn
|
||||
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
|
||||
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
|
||||
apt-get update && \
|
||||
apt-get install -y yarn
|
||||
|
||||
# Install app dependencies, compile assets, and set up the database
|
||||
RUN bundle install && \
|
||||
yarn install && \
|
||||
if [ "$RAILS_ENV" = "development" ] ; then \
|
||||
echo "Starting webpack-dev-server..." && \
|
||||
bin/webpack-dev-server & \
|
||||
else \
|
||||
echo "Compiling webpack assets..." && \
|
||||
rails webpacker:compile ; \
|
||||
fi && \
|
||||
rails db:setup && \
|
||||
rake db:migrate && \
|
||||
rm -f tmp/pids/server.pid
|
||||
|
||||
# This image should expose the port 3000.
|
||||
# This does not actually expose the port, you'll have to expose it yourself by
|
||||
# using `-p 3000:3000/tcp` in Docker's CLI or `- "3000:3000"` in the in docker-compose.yml service's ports[].
|
||||
# https://docs.docker.com/engine/reference/builder/#expose
|
||||
#EXPOSE 3000/tcp
|
||||
|
||||
# This image should expose port 3000.
|
||||
EXPOSE 3000/tcp
|
||||
|
||||
# Finally, start the server using Puma!
|
||||
CMD bundle exec puma -C config/puma.rb -e ${RAILS_ENV} -b tcp://0.0.0.0:3000
|
||||
|
||||
# And run it with
|
||||
# docker run --name nb-webserver -p 80:3000 -d notebookai
|
||||
2
Gemfile
2
Gemfile
@ -115,7 +115,7 @@ group :production do
|
||||
end
|
||||
|
||||
group :test, :production do
|
||||
gem 'pg', '~> 1.4'
|
||||
gem 'pg', '~> 1.5'
|
||||
|
||||
gem "mini_racer", "~> 0.6.3" # TODO: audit whether we can remove this
|
||||
end
|
||||
|
||||
26
Gemfile.lock
26
Gemfile.lock
@ -133,7 +133,7 @@ GEM
|
||||
autoprefixer-rails (10.4.7.0)
|
||||
execjs (~> 2)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.746.0)
|
||||
aws-partitions (1.760.0)
|
||||
aws-sdk (3.1.0)
|
||||
aws-sdk-resources (~> 3)
|
||||
aws-sdk-accessanalyzer (1.23.0)
|
||||
@ -331,7 +331,7 @@ GEM
|
||||
aws-sdk-connectparticipant (1.14.0)
|
||||
aws-sdk-core (~> 3, >= 3.120.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-core (3.171.0)
|
||||
aws-sdk-core (3.171.1)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
@ -594,7 +594,7 @@ GEM
|
||||
aws-sdk-kinesisvideosignalingchannels (1.13.0)
|
||||
aws-sdk-core (~> 3, >= 3.120.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-kms (1.63.0)
|
||||
aws-sdk-kms (1.64.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-lakeformation (1.17.0)
|
||||
@ -1102,7 +1102,7 @@ GEM
|
||||
aws-sdk-route53resolver (1.30.0)
|
||||
aws-sdk-core (~> 3, >= 3.120.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.120.1)
|
||||
aws-sdk-s3 (1.122.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.4)
|
||||
@ -1280,7 +1280,7 @@ GEM
|
||||
bundler-audit (0.9.1)
|
||||
bundler (>= 1.2.0, < 3)
|
||||
thor (~> 1.0)
|
||||
chartkick (4.1.3)
|
||||
chartkick (5.0.2)
|
||||
climate_control (0.2.0)
|
||||
cocoon (1.2.15)
|
||||
codeclimate-test-reporter (1.0.9)
|
||||
@ -1475,7 +1475,7 @@ GEM
|
||||
net-protocol
|
||||
netrc (0.11.0)
|
||||
newrelic_rpm (9.2.0)
|
||||
nio4r (2.5.8)
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.14.3)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
@ -1499,7 +1499,7 @@ GEM
|
||||
faraday (~> 1.4)
|
||||
faraday_middleware (~> 1)
|
||||
paypalhttp (1.0.1)
|
||||
pg (1.4.6)
|
||||
pg (1.5.3)
|
||||
pkg-config (1.5.1)
|
||||
pry (0.14.2)
|
||||
coderay (~> 1.1)
|
||||
@ -1543,7 +1543,7 @@ GEM
|
||||
rails (>= 3.2)
|
||||
rails-ujs (0.1.0)
|
||||
railties (>= 3.1)
|
||||
rails_admin (3.1.1)
|
||||
rails_admin (3.1.2)
|
||||
activemodel-serializers-xml (>= 1.0)
|
||||
kaminari (>= 0.14, < 2.0)
|
||||
nested_form (~> 0.3)
|
||||
@ -1598,7 +1598,7 @@ GEM
|
||||
sprockets (> 3.0)
|
||||
sprockets-rails
|
||||
tilt
|
||||
selenium-webdriver (4.8.0)
|
||||
selenium-webdriver (4.9.1)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
@ -1635,7 +1635,7 @@ GEM
|
||||
stripe (>= 2.8, < 9)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
terser (1.1.13)
|
||||
terser (1.1.15)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
text-hyphen (1.4.1)
|
||||
textstat (0.1.8)
|
||||
@ -1643,9 +1643,9 @@ GEM
|
||||
thor (1.2.1)
|
||||
tilt (2.0.11)
|
||||
timeago_js (3.0.2.2)
|
||||
timeout (0.3.1)
|
||||
timeout (0.3.2)
|
||||
tribute (3.6.0.0)
|
||||
turbo-rails (1.3.3)
|
||||
turbo-rails (1.4.0)
|
||||
actionpack (>= 6.0.0)
|
||||
activejob (>= 6.0.0)
|
||||
railties (>= 6.0.0)
|
||||
@ -1724,7 +1724,7 @@ DEPENDENCIES
|
||||
paranoia
|
||||
paypal-checkout-sdk
|
||||
paypal_client
|
||||
pg (~> 1.4)
|
||||
pg (~> 1.5)
|
||||
pry
|
||||
puma (~> 5.6)
|
||||
puma-heroku
|
||||
|
||||
BIN
app/assets/images/basil/character-jam.png
Normal file
BIN
app/assets/images/basil/character-jam.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
BIN
app/assets/images/placeholders/commission.png
Normal file
BIN
app/assets/images/placeholders/commission.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 98 KiB |
BIN
app/assets/images/placeholders/loading.gif
Normal file
BIN
app/assets/images/placeholders/loading.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
BIN
app/assets/images/placeholders/loading2.gif
Normal file
BIN
app/assets/images/placeholders/loading2.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@ -1,3 +1,47 @@
|
||||
// Place all the styles related to the Basil controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: https://sass-lang.com/
|
||||
$small-screen-up: 601px !default;
|
||||
$medium-screen-up: 993px !default;
|
||||
$large-screen-up: 1201px !default;
|
||||
$small-screen: 600px !default;
|
||||
$medium-screen: 992px !default;
|
||||
$large-screen: 1200px !default;
|
||||
|
||||
$medium-and-up: "only screen and (min-width : #{$small-screen-up})" !default;
|
||||
$large-and-up: "only screen and (min-width : #{$medium-screen-up})" !default;
|
||||
$small-and-down: "only screen and (max-width : #{$small-screen})" !default;
|
||||
$medium-and-down: "only screen and (max-width : #{$medium-screen})" !default;
|
||||
$medium-only: "only screen and (min-width : #{$small-screen-up}) and (max-width : #{$medium-screen})" !default;
|
||||
|
||||
@mixin column-count($ct) {
|
||||
-webkit-column-count: $ct;
|
||||
-moz-column-count: $ct;
|
||||
column-count: $ct;
|
||||
}
|
||||
|
||||
.cards-container {
|
||||
column-break-inside: avoid;
|
||||
.card {
|
||||
display: inline-block;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
@media #{$small-and-down} {
|
||||
.cards-container {
|
||||
@include column-count(1);
|
||||
}
|
||||
}
|
||||
@media #{$medium-and-up} {
|
||||
.cards-container {
|
||||
@include column-count(2);
|
||||
}
|
||||
}
|
||||
@media #{$large-and-up} {
|
||||
.cards-container {
|
||||
@include column-count(3);
|
||||
}
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class BasilController < ApplicationController
|
||||
before_action :authenticate_user!, except: [:complete_commission, :about, :stats]
|
||||
before_action :authenticate_user!, except: [:complete_commission, :about, :stats, :jam, :queue_jam_job, :commission_info]
|
||||
|
||||
before_action :require_admin_access, only: [:review], unless: -> { Rails.env.development? }
|
||||
|
||||
@ -210,6 +210,51 @@ class BasilController < ApplicationController
|
||||
def about
|
||||
end
|
||||
|
||||
def jam
|
||||
@recent_commissions = BasilCommission.where(entity_id: nil).order('id DESC').limit(24)
|
||||
@total_count = BasilCommission.where(entity_id: nil).count
|
||||
|
||||
# For generating pie charts
|
||||
@all_commissions = BasilCommission.where(entity_id: nil)
|
||||
end
|
||||
|
||||
def queue_jam_job
|
||||
created_prompt = [
|
||||
jam_params[:age],
|
||||
*jam_params[:features]
|
||||
].compact.join(', ')
|
||||
|
||||
# Create our commission, then redirect back to preview it
|
||||
BasilCommission.create!(
|
||||
user: current_user,
|
||||
entity: nil,
|
||||
prompt: created_prompt,
|
||||
job_id: SecureRandom.uuid,
|
||||
style: ["realistic", "realistic2", "realistic3"].sample,
|
||||
final_settings: jam_params
|
||||
)
|
||||
|
||||
redirect_back(fallback_location: basil_jam_path, notice: "#{jam_params.fetch(:name, '').presence || 'Your character'} will be visualized shortly. Find them on this page!")
|
||||
end
|
||||
|
||||
def commission_info
|
||||
@commission = BasilCommission.find_by(job_id: params[:jobid])
|
||||
raise "No BasilCommission with ID #{params[:jobid]}" if @commission.nil?
|
||||
|
||||
render json: {
|
||||
image_url: @commission.image.url,
|
||||
user_id: @commission.user_id,
|
||||
prompt: @commission.prompt,
|
||||
job_id: @commission.job_id,
|
||||
created_at: @commission.created_at,
|
||||
updated_at: @commission.updated_at,
|
||||
completed_at: @commission.completed_at,
|
||||
style: @commission.style,
|
||||
final_settings: @commission.final_settings,
|
||||
cached_seconds_taken: @commission.cached_seconds_taken,
|
||||
}
|
||||
end
|
||||
|
||||
def stats
|
||||
@commissions = BasilCommission.all.with_deleted
|
||||
|
||||
@ -509,10 +554,11 @@ class BasilController < ApplicationController
|
||||
|
||||
def complete_commission
|
||||
commission = BasilCommission.find_by(job_id: params[:jobid])
|
||||
return if commission.nil?
|
||||
|
||||
commission.update(completed_at: DateTime.current,
|
||||
final_settings: JSON.parse(params[:settings]))
|
||||
raise "Tried to complete commission with invalid job ID #{params[:jobid]}" if commission.nil?
|
||||
|
||||
merged_settings = commission.final_settings || {}
|
||||
commission.update!(completed_at: DateTime.current,
|
||||
final_settings: merged_settings.merge(JSON.parse(params.fetch(:settings, "{}"))))
|
||||
|
||||
# Attach the image in S3 to our `image` ActiveStorage relation
|
||||
key = "job-#{params[:jobid]}.png"
|
||||
@ -554,6 +600,7 @@ class BasilController < ApplicationController
|
||||
@commissions = BasilCommission.where(id: @reviewed_commission_ids.pluck(:basil_commission_id))
|
||||
.where.not(completed_at: nil)
|
||||
.where(user: current_user)
|
||||
.where.not(entity_type: nil, entity_id: nil)
|
||||
.order(created_at: :desc)
|
||||
.limit(50)
|
||||
.includes(:entity)
|
||||
@ -565,6 +612,7 @@ class BasilController < ApplicationController
|
||||
@commissions = BasilCommission.where.not(id: @reviewed_commission_ids)
|
||||
.where.not(completed_at: nil)
|
||||
.where(user: current_user)
|
||||
.where.not(entity_type: nil, entity_id: nil)
|
||||
.order(created_at: :desc)
|
||||
.limit(50)
|
||||
.includes(:entity)
|
||||
@ -595,4 +643,8 @@ class BasilController < ApplicationController
|
||||
def commission_params
|
||||
params.require(:basil_commission).permit(:style, :entity_type, :entity_id, field: {})
|
||||
end
|
||||
|
||||
def jam_params
|
||||
params.require(:commission).permit(:name, :age, features: [])
|
||||
end
|
||||
end
|
||||
|
||||
@ -168,6 +168,12 @@ class ContentController < ApplicationController
|
||||
if user_signed_in? && current_user.can_create?(@content.class) \
|
||||
|| PermissionService.user_has_active_promotion_for_this_content_type(user: current_user, content_type: @content.class.name)
|
||||
|
||||
# For users who are creating premium content in a collaborated universe without premium of their own
|
||||
# we want to default that content into one of their collaborated unvierses.
|
||||
if !current_user.on_premium_plan? && Rails.application.config.content_types[:premium].map(&:name).include?(@content.class.name)
|
||||
@content.universe_id = current_user.contributable_universes.first.try(:id)
|
||||
end
|
||||
|
||||
if params.key?(:document_entity)
|
||||
entity = DocumentEntity.find_by(id: params.fetch(:document_entity).to_i)
|
||||
if entity.document_owner == current_user
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
class CollaborationMailer < ApplicationMailer
|
||||
default from: "collaboration@notebook.ai"
|
||||
|
||||
def contributor_invitation(inviter:, invite_email:, universe:)
|
||||
def contributor_invitation(inviter, invite_email, universe)
|
||||
@inviter = inviter
|
||||
@universe = universe
|
||||
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
class BasilCommission < ApplicationRecord
|
||||
acts_as_paranoid
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :entity, polymorphic: true
|
||||
belongs_to :user, optional: true
|
||||
belongs_to :entity, polymorphic: true, optional: true
|
||||
|
||||
has_one_attached :image,
|
||||
service: :amazon_basil,
|
||||
@ -18,8 +18,7 @@ class BasilCommission < ApplicationRecord
|
||||
|
||||
# TODO clean this up and put it in a service
|
||||
sts_client = Aws::STS::Client.new(region: region)
|
||||
queue_url = 'https://sqs.' + region + '.amazonaws.com/' +
|
||||
sts_client.get_caller_identity.account + '/' + queue_name
|
||||
queue_url = 'https://sqs.' + region + '.amazonaws.com/' + sts_client.get_caller_identity.account + '/' + queue_name
|
||||
sqs_client = Aws::SQS::Client.new(region: region)
|
||||
|
||||
message_body = {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
class ContributorService < Service
|
||||
def self.invite_contributor_to_universe universe:, email:
|
||||
def self.invite_contributor_to_universe(universe:, email:)
|
||||
# First, look up whether a user already exists for this invite
|
||||
related_user = User.find_by(email: email.downcase)
|
||||
|
||||
@ -31,19 +31,19 @@ class ContributorService < Service
|
||||
end
|
||||
end
|
||||
|
||||
def self.send_invite_email_to inviter:, email:, universe:
|
||||
def self.send_invite_email_to(inviter:, email:, universe:)
|
||||
CollaborationMailer.contributor_invitation(
|
||||
inviter: inviter,
|
||||
invite_email: email,
|
||||
universe: universe
|
||||
inviter,
|
||||
email,
|
||||
universe
|
||||
).deliver_now! if Rails.env.production?
|
||||
end
|
||||
|
||||
def self.send_contributor_notice_to inviter:, email:, universe:
|
||||
def self.send_contributor_notice_to(inviter:, email:, universe:)
|
||||
CollaborationMailer.contributor_invitation(
|
||||
inviter: inviter,
|
||||
invite_email: email,
|
||||
universe: universe
|
||||
inviter,
|
||||
email,
|
||||
universe
|
||||
).deliver_now! if Rails.env.production?
|
||||
end
|
||||
end
|
||||
|
||||
@ -61,8 +61,8 @@ class ExportService < Service
|
||||
if value_for_this_field.present?
|
||||
json_list = JSON.parse(value_for_this_field.value)
|
||||
formatted_names = json_list.map do |link_code|
|
||||
query_link_code_with_cache(*link_code.split('-')).name
|
||||
end
|
||||
query_link_code_with_cache(*link_code.split('-')).try(:name)
|
||||
end.compact
|
||||
|
||||
export_text << " #{field.label}: #{formatted_names.to_sentence}\n"
|
||||
else
|
||||
@ -310,8 +310,8 @@ class ExportService < Service
|
||||
# TODO: we should probably whitelist content_type from valid page types here
|
||||
|
||||
# If there's no cache, we unfortunately need to do a query to resolve the link code
|
||||
content = class_from_name(content_type_name).find(content_id)
|
||||
@content_cache[cache_key] = content
|
||||
content = class_from_name(content_type_name).find_by(id: content_id)
|
||||
@content_cache[cache_key] = content if content.present?
|
||||
|
||||
return content
|
||||
end
|
||||
|
||||
478
app/views/basil/_character_jam.html.erb
Normal file
478
app/views/basil/_character_jam.html.erb
Normal file
@ -0,0 +1,478 @@
|
||||
<!--
|
||||
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', 'Construct', 'Half-Elf', 'Half-Dwarf', 'Half-Orc', 'Spirit'] %>
|
||||
<% 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>
|
||||
@ -1,224 +0,0 @@
|
||||
<script type="text/javascript">
|
||||
function commission_basil(style) {
|
||||
// Set style hidden value to our selected style
|
||||
$('#basil_commission_style').val(style);
|
||||
|
||||
// Submit form to start the commission
|
||||
$('#new_basil_commission').submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<%= 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 %>
|
||||
<div class="col s12 m4">
|
||||
<div style="margin: 1em 0">
|
||||
<%= link_to basil_path, class: 'grey-text text-darken-2' do %>
|
||||
<i class="material-icons left">chevron_left</i>
|
||||
Back to my character list
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="clearfix">
|
||||
<%= image_tag @character.random_image_including_private(format: :medium), style: 'width: 100%' %>
|
||||
<h1 style="font-size: 2rem; margin-bottom: 0"><%= link_to @character.name, @character %></h1>
|
||||
<%= link_to 'Edit this character page', edit_polymorphic_path(@character), class: 'grey-text text-darken-2' %>
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
<% if @gender_field && @gender_value %>
|
||||
<li style="margin-bottom: 1em">
|
||||
<div class="grey-text text-darken-3" style="font-weight: bold; font-size: 0.8em">
|
||||
Gender
|
||||
<%= range_field_tag "field[#{@gender_field.id}]", @guidance.fetch(@gender_field.id.to_s, 1), { min: 0, max: 1.3, step: 0.1, style: 'width: 50%', class: 'js-importance-slider hide' } %>
|
||||
</div>
|
||||
<div>
|
||||
<%= @gender_value %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% if @age_field && @gender_value %>
|
||||
<li style="margin-bottom: 1em">
|
||||
<div class="grey-text text-darken-3" style="font-weight: bold; font-size: 0.8em">
|
||||
Age
|
||||
<%= range_field_tag "field[#{@age_field.id}]", @guidance.fetch(@age_field.id.to_s, 1), { min: 0, max: 1.3, step: 0.1, style: 'width: 50%', class: 'js-importance-slider hide' } %>
|
||||
</div>
|
||||
<div>
|
||||
<%= @age_value %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
|
||||
<% 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
|
||||
%>
|
||||
<li style="margin-bottom: 1em;">
|
||||
<div class="grey-text text-darken-3" style="font-weight: bold; font-size: 0.8em">
|
||||
<%= field.label %>
|
||||
<%= range_field_tag "field[#{field.id}]", @guidance.fetch(field.id.to_s, 1), { min: 0, max: 1.3, step: 0.1, style: 'width: 50%', class: 'js-importance-slider hide' } %>
|
||||
</div>
|
||||
<div>
|
||||
<%= value %>
|
||||
</div>
|
||||
</li>
|
||||
<% end %>
|
||||
</ul>
|
||||
|
||||
<% if !shown_any_value %>
|
||||
<div class="red card-panel lighten-3">
|
||||
Basil works best with guidance! Please fill out some fields in the "Looks" category for this character
|
||||
before requesting an image.
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div>
|
||||
<% if @can_request_another %>
|
||||
<%= 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')" %>
|
||||
<% end %>
|
||||
|
||||
<div class="card-panel js-importance-slider hide" style="margin-right: 1em">
|
||||
<strong>How to customize per-field importance</strong>
|
||||
<br /><br />
|
||||
|
||||
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.
|
||||
<br /><br />
|
||||
You can also tell Basil to ignore a field entirely by dragging the slider all the way to the left.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-panel" style="margin-top: 2em">
|
||||
<p>
|
||||
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!
|
||||
</p>
|
||||
<p>
|
||||
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' %>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m2" style="margin-top: 1rem">
|
||||
<% if @can_request_another && shown_any_value %>
|
||||
<div class="grey-text center"><strong>Available styles</strong></div>
|
||||
<% %w(realistic painting sketch digital anime abstract).each do |style| %>
|
||||
<%= link_to "javascript:commission_basil('#{style}')" do %>
|
||||
<div class="hoverable card-panel purple white-text">
|
||||
<%= style.humanize %>
|
||||
<i class="material-icons right">chevron_right</i>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @can_request_another && shown_any_value %>
|
||||
<div class="grey-text center"><strong>Experimental styles</strong></div>
|
||||
<% %w(painting2 horror watercolor).each do |style| %>
|
||||
<%= link_to "javascript:commission_basil('#{style}')" do %>
|
||||
<div class="hoverable card-panel purple white-text">
|
||||
<%= style.humanize %>
|
||||
<i class="material-icons right">chevron_right</i>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if !@can_request_another %>
|
||||
<div class="card-panel purple white-text">
|
||||
Basil is working on your <%= pluralize @in_progress_commissions.count, 'requested commission' %>.
|
||||
<br /><br />
|
||||
As soon as he completes one, you'll be able to request another.
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="col s12 m6" style="margin-top: 1rem">
|
||||
<% @commissions.each do |commission| %>
|
||||
<div>
|
||||
<% if commission.complete? %>
|
||||
<%# image_tag commission.image, style: 'width: 100%' %>
|
||||
<%
|
||||
s3 = Aws::S3::Resource.new(region: "us-east-1")
|
||||
obj = s3.bucket(commission.s3_bucket).object("job-#{commission.job_id}.png")
|
||||
%>
|
||||
<div class="card horizontal">
|
||||
<div class="card-image">
|
||||
<%= link_to obj.presigned_url(:get) do %>
|
||||
<%= image_tag obj.presigned_url(:get) %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="card-stacked">
|
||||
<div class="card-content">
|
||||
<div>
|
||||
<strong><%= @character.name %></strong>
|
||||
<% if commission.style? %>
|
||||
<em>(<%= commission.style.humanize %>)</em>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="grey-text text-darken-2">
|
||||
<span style="font-size: 0.8em">Completed <%= time_ago_in_words commission.completed_at %> ago</span>
|
||||
·<br />
|
||||
<span style="font-size: 0.8em">Took <%= distance_of_time_in_words commission.completed_at - commission.created_at %></span>
|
||||
</div>
|
||||
<div style="margin-top: 1em">
|
||||
<div class="center" style="font-size: 0.9em"><strong>Feedback for Basil</strong></div>
|
||||
<div class="row">
|
||||
<%= form_for commission.basil_feedbacks.find_or_initialize_by(user: current_user), url: basil_feedback_path(commission.job_id), method: :POST, remote: true do |f| %>
|
||||
<div class="col s3 red lighten-3">
|
||||
<label>
|
||||
<%= f.radio_button :score_adjustment, '-2', { class: 'autosave-closest-form-on-change' } %>
|
||||
<span class="black-text">:'(</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col s3 orange lighten-3">
|
||||
<label>
|
||||
<%= f.radio_button :score_adjustment, '-1', { class: 'autosave-closest-form-on-change' } %>
|
||||
<span class="black-text">:(</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col s3 green lighten-3">
|
||||
<label>
|
||||
<%= f.radio_button :score_adjustment, '1', { class: 'autosave-closest-form-on-change' } %>
|
||||
<span class="black-text">:)</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col s3 blue lighten-3">
|
||||
<label>
|
||||
<%= f.radio_button :score_adjustment, '2', { class: 'autosave-closest-form-on-change' } %>
|
||||
<span class="black-text">:D</span>
|
||||
</label>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="card-action">
|
||||
<%= link_to "Save to #{@character.name}", '#', class: 'purple-text' %>
|
||||
<%= link_to "Delete", '#', class: 'red-text right' %>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="card-panel green white-text darken-4">
|
||||
Basil is still working on this commission... (style: <%= commission.style %>)
|
||||
<div style="font-size: 0.8em">
|
||||
(Requested <%= time_ago_in_words(commission.created_at) %> ago)
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
444
app/views/basil/jam.html.erb
Normal file
444
app/views/basil/jam.html.erb
Normal file
@ -0,0 +1,444 @@
|
||||
<div class="row">
|
||||
<div class="col s12 m12 l6">
|
||||
<h1 class="text-center" style="font-size: 2rem">
|
||||
<i class="material-icons <%= Character.text_color %>"><%= Character.icon %></i>
|
||||
The Character VizJam has concluded!
|
||||
</h1>
|
||||
|
||||
<div class="center" style="padding: 10px">
|
||||
<%= link_to asset_path('basil/character-jam.png') do %>
|
||||
<%= image_tag 'basil/character-jam.png', style: 'width: 100%' %>
|
||||
<% end %>
|
||||
<br />
|
||||
Thank you for participating!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m12 l6">
|
||||
<h2 style="font-size: 1.6rem; margin-top: 3rem">Together, we visualized 1,611 characters!</h2>
|
||||
<p>
|
||||
In the spirit of AI transparency, I've compiled some aggregate visualizations of the kinds of characters
|
||||
that were most — and least — visualized during this VizJam.
|
||||
</p>
|
||||
|
||||
<ul class="collapsible">
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Age</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= column_chart(
|
||||
{ "Infant" => 36, "Child" => 33, "Teenager" => 275, "Young Adult" => 467, "Adult" => 751, "Old" => 146, "Very Old" => 24 },
|
||||
colors: ['#2196F3'],
|
||||
max: 760,
|
||||
label: 'Visualizations'
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">pie_chart</i> Gender</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= pie_chart(
|
||||
{ "Male" => 508, "Female" => 628, "Ambiguous" => 92, "Transgender" => 63, "Non-binary" => 81, "Agender" => 33, "Androgenous" => 146, "Genderqueer" => 28 },
|
||||
legend: 'right',
|
||||
suffix: ' visualizations'
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Skin tone</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= bar_chart(
|
||||
{"Light"=>348,
|
||||
"Pale"=>314,
|
||||
"Olive"=>233,
|
||||
"Black"=>227,
|
||||
"Brown"=>188,
|
||||
"White"=>185,
|
||||
"Fair"=>169,
|
||||
"Dark"=>165,
|
||||
"Tan"=>153,
|
||||
"Medium"=>151,
|
||||
"Freckled"=>99,
|
||||
"Blue"=>42,
|
||||
"Grey"=>29,
|
||||
"Silver"=>27,
|
||||
"Chocolate"=>27,
|
||||
"Gold"=>25,
|
||||
"Green"=>24,
|
||||
"Speckled"=>22,
|
||||
"Albino"=>21,
|
||||
"Purple"=>19,
|
||||
"Pink"=>17,
|
||||
"Red"=>15,
|
||||
"Yellow"=>11,
|
||||
"Orange"=>10},
|
||||
colors: ['#2196F3'],
|
||||
label: 'Visualizations',
|
||||
height: '600px',
|
||||
max: 350
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Face</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= bar_chart(
|
||||
{"Brown"=>285,
|
||||
"Blue"=>218,
|
||||
"Green"=>161,
|
||||
"Amber"=>96,
|
||||
"Grey"=>90,
|
||||
"Hazel"=>87,
|
||||
"Topaz"=>66,
|
||||
"Black"=>61,
|
||||
"Amethyst"=>50,
|
||||
"Red"=>43,
|
||||
"Violet"=>43,
|
||||
"White"=>28,
|
||||
"Indigo"=>25},
|
||||
colors: ['#2196F3'],
|
||||
title: 'Eye color',
|
||||
height: '500px',
|
||||
label: 'Visualizations'
|
||||
)
|
||||
%>
|
||||
<br />
|
||||
<%= bar_chart({
|
||||
"Stubble"=>168,
|
||||
"Beard"=>125,
|
||||
"Chinstrap"=>114,
|
||||
"Mustache"=>113,
|
||||
"Goatee"=>61,
|
||||
"Patchy"=>40,
|
||||
"Sideburns"=>28,
|
||||
"Mutton chops"=>16,
|
||||
"Soul patch"=>9,
|
||||
"Neckbeard"=>7,
|
||||
"Horseshoe mustache"=>5,
|
||||
"Fu Manchu"=>5,
|
||||
"Handlebar mustache"=>4,
|
||||
"Chin curtain"=>3},
|
||||
title: 'Facial hair (if any)',
|
||||
colors: ['#2196F3'],
|
||||
max: 170,
|
||||
height: '500px',
|
||||
label: 'Visualizations'
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Hair</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= bar_chart({
|
||||
"Black"=>375,
|
||||
"Brown"=>359,
|
||||
"Blonde"=>216,
|
||||
"Red"=>93,
|
||||
"Blue"=>88,
|
||||
"White"=>79,
|
||||
"Auburn"=>68,
|
||||
"Orange"=>54,
|
||||
"Pink"=>53,
|
||||
"Purple"=>48,
|
||||
"Greying"=>40,
|
||||
"Bleached"=>39,
|
||||
"Grey"=>32,
|
||||
"Rainbow"=>30,
|
||||
"Green"=>29,
|
||||
"Bald"=>7},
|
||||
colors: ['#2196F3'],
|
||||
title: 'Colors',
|
||||
height: '500px',
|
||||
label: 'Visualizations')
|
||||
%>
|
||||
<br />
|
||||
<%=
|
||||
bar_chart({
|
||||
"Long"=>617,
|
||||
"Medium"=>394,
|
||||
"Wavy"=>288,
|
||||
"Straight"=>242,
|
||||
"Short"=>231,
|
||||
"Curly"=>186,
|
||||
"Ponytail"=>111,
|
||||
"Bangs"=>99,
|
||||
"Braided"=>97,
|
||||
"Parted"=>88,
|
||||
"Fringe"=>70,
|
||||
"Bob cut"=>59,
|
||||
"Emo"=>48,
|
||||
"Slicked back"=>46,
|
||||
"Dreadlocks"=>46,
|
||||
"Pixie"=>41,
|
||||
"Bun"=>40,
|
||||
"Feathered"=>39,
|
||||
"Afro"=>36,
|
||||
"Rocker"=>31,
|
||||
"Spiked"=>29,
|
||||
"Pigtails"=>22,
|
||||
"Mop-top"=>20,
|
||||
"Buzzcut"=>20,
|
||||
"Bald"=>19,
|
||||
"Crewcut"=>18,
|
||||
"Balding"=>13,
|
||||
"Cornrows"=>11,
|
||||
"Flattop"=>10,
|
||||
"Chignon"=>9,
|
||||
"Bowl cut"=>9,
|
||||
"Rat-tail"=>8,
|
||||
"Pompadour"=>7,
|
||||
"Bouffant"=>7,
|
||||
"Combover"=>6},
|
||||
colors: ['#2196F3'],
|
||||
max: 620,
|
||||
height: '900px',
|
||||
title: 'Styles',
|
||||
label: 'Visualizations'
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Body</div>
|
||||
<div class="collapsible-body white">
|
||||
<%=
|
||||
bar_chart({
|
||||
"Lean"=>395,
|
||||
"Thin"=>336,
|
||||
"Athletic"=>329,
|
||||
"Curvy"=>188,
|
||||
"Hourglass"=>149,
|
||||
"Muscular"=>144,
|
||||
"Rectangular"=>130,
|
||||
"Petite"=>106,
|
||||
"Round"=>77,
|
||||
"Frail"=>74,
|
||||
"Big-boned"=>73,
|
||||
"Pear-shaped"=>73,
|
||||
"Underweight"=>66,
|
||||
"Overweight"=>60
|
||||
},
|
||||
colors: ['#2196F3'],
|
||||
height: '500px',
|
||||
label: 'Visualizations')
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="collapsible-header"><i class="material-icons">bar_chart</i> Non-human races</div>
|
||||
<div class="collapsible-body white">
|
||||
<%= bar_chart(
|
||||
{"Angel"=>69,
|
||||
"Fey"=>67,
|
||||
"Fairy"=>59,
|
||||
"Vampire"=>54,
|
||||
"Elf"=>52,
|
||||
"Animal"=>51,
|
||||
"Reptilian"=>48,
|
||||
"Werewolf"=>44,
|
||||
"Elemental"=>33,
|
||||
"Robot"=>29,
|
||||
"Android"=>26,
|
||||
"Bird"=>25,
|
||||
"Insectoid"=>18,
|
||||
"Genie"=>15,
|
||||
"Orc"=>13,
|
||||
"Halfling"=>13,
|
||||
"Dwarf"=>11,
|
||||
"Gnome"=>10,
|
||||
"Arachnoid"=>10},
|
||||
colors: ['#2196F3'],
|
||||
max: 70,
|
||||
height: '500px',
|
||||
label: 'Visualizations'
|
||||
)
|
||||
%>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p style="font-size: 0.8em">
|
||||
If you noticed that any particular age, gender, race, or other trait produced
|
||||
lower-quality images than others, please <%= link_to 'let me know', 'https://discord.gg/bDE8g5YRzp' %>
|
||||
so I can continue to make our AI models work better for <em>all</em> kinds of characters!
|
||||
</p>
|
||||
|
||||
<br />
|
||||
|
||||
<div class="grey lighten-4">
|
||||
<%= area_chart({
|
||||
"2023-06-08"=>208,
|
||||
"2023-06-09"=>515,
|
||||
"2023-06-10"=>255,
|
||||
"2023-06-11"=>128,
|
||||
"2023-06-12"=>167,
|
||||
"2023-06-13"=>338}, title: 'New visualizations per day', colors: ['#E91E63'], max: 550, height: '200px')
|
||||
%>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%
|
||||
@content_type = Character
|
||||
singular_class_name = @content_type.name
|
||||
pluralized_class_name = @content_type.name.pluralize
|
||||
premium_page = !User.new.can_create?(@content_type)
|
||||
%>
|
||||
|
||||
<div class="row">
|
||||
<div class="col s12">
|
||||
<h1 class="text-center" style="font-size: 2rem">
|
||||
You can still create characters and visualize them on Notebook.ai!
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="col s12">
|
||||
<div class="card horizontal">
|
||||
<div class="card-image">
|
||||
<%= image_tag "card-headers/#{pluralized_class_name.downcase}.webp", class: 'materialboxed tooltipped', alt: "The default image used for all #{pluralized_class_name.downcase} on Notebook.ai, but you can replace it with your own uploads.", data: { tooltip: "The default image used for all #{pluralized_class_name.downcase} on Notebook.ai, but you can replace it with your own uploads."} %>
|
||||
</div>
|
||||
<div class="card-stacked">
|
||||
<div class="card-content spaced-paragraphs">
|
||||
<h1 class="card-title <%= @content_type.text_color %>">Creating <%= pluralized_class_name %> on Notebook.ai</h1>
|
||||
<p><em>
|
||||
<%= t("content_descriptions.#{singular_class_name.downcase}") %></em>
|
||||
</p>
|
||||
<p>
|
||||
Creating <%= pluralized_class_name.downcase %> on Notebook.ai is easy.
|
||||
</p>
|
||||
<p>
|
||||
To get started, just click <strong><%= pluralized_class_name %></strong> under the "Worldbuilding" header in the site sidebar.
|
||||
You'll be able to see or edit all of your existing <%= singular_class_name.downcase %> pages and create new ones at any time.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col s12 m12 l5">
|
||||
<div class="card">
|
||||
<div class="card-content">
|
||||
<h2 class="card-title">
|
||||
<i class="material-icons <%= @content_type.text_color %> left">
|
||||
<%= @content_type.icon %>
|
||||
</i>
|
||||
|
||||
Get a head start with a rich
|
||||
<span class="<%= @content_type.text_color %>"><%= singular_class_name.downcase %></span>
|
||||
template
|
||||
</h2>
|
||||
<br />
|
||||
|
||||
<div class="spaced-paragraphs">
|
||||
<p>
|
||||
Templates on Notebook.ai are what help our unique worldbuilding system better understand your world.
|
||||
</p>
|
||||
<p>
|
||||
You can fill out as little or as much as you'd like on every new <%= singular_class_name.downcase %>. You'll see
|
||||
progress indicators every time you edit it to show where you can make progress on, and our system will
|
||||
intelligently generate questions for you around the site that will automatically save your answers
|
||||
to the proper place on your <%= singular_class_name.downcase %> page.
|
||||
</p>
|
||||
<p>
|
||||
Templates are also fully customizable across every <%= singular_class_name.downcase %> in your notebook.
|
||||
</p>
|
||||
<p>
|
||||
You can browse the default template for <%= pluralized_class_name.downcase %> here; click any category
|
||||
to see its questions.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col s12 m12 l7">
|
||||
<ul class="collapsible popout">
|
||||
<%
|
||||
YAML.load_file(Rails.root.join('config', 'attributes', "#{singular_class_name.downcase}.yml")).map do |category_name, category_details|
|
||||
%>
|
||||
<% next if category_name == :contributors %>
|
||||
<li>
|
||||
<div class="collapsible-header <%= @content_type.color %> darken-3 white-text">
|
||||
<i class="material-icons"><%= category_details[:icon] %></i>
|
||||
<%= category_details[:label] %>
|
||||
</div>
|
||||
<div class="collapsible-body">
|
||||
<% if category_name == :gallery %>
|
||||
<p>
|
||||
This category lets you upload images to this <%= singular_class_name.downcase %>'s notebook page.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
It's great if you have sketches or artwork for your <%= singular_class_name.downcase %>,
|
||||
but also works well for collecting visual inspiration, too!
|
||||
</p>
|
||||
<% end %>
|
||||
<% category_details.fetch(:attributes, []).each do |field| %>
|
||||
<div>
|
||||
<strong><%= field[:label] %></strong>
|
||||
</div>
|
||||
<div>
|
||||
<% if field[:field_type] == 'link' || field[:field_type] == 'universe' %>
|
||||
This field allows you to link your other Notebook.ai pages to this <%= singular_class_name.downcase %>.
|
||||
<% elsif field[:field_type] == 'tags' %>
|
||||
This field lets you add clickable tags to your <%= pluralized_class_name.downcase %>.
|
||||
<% else %>
|
||||
<%=
|
||||
I18n.translate "attributes.#{singular_class_name.downcase}.#{field[:label].downcase.gsub(/\s/, '_')}",
|
||||
scope: :serendipitous_questions,
|
||||
name: "this #{singular_class_name.downcase}",
|
||||
default: 'Write as little or as much as you want!'
|
||||
%>
|
||||
<br />
|
||||
<span class="grey-text"><%= field[:description].try(:html_safe) %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
<br />
|
||||
<% end %>
|
||||
</div>
|
||||
</li>
|
||||
<%
|
||||
end
|
||||
%>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col s12">
|
||||
<div class="card horizontal">
|
||||
<div class="card-image">
|
||||
<%= image_tag 'basil/portrait.png', style: 'width: 420px' %>
|
||||
</div>
|
||||
<div class="card-stacked">
|
||||
<div class="card-content">
|
||||
<div class="card-title">
|
||||
<i class="pink-text material-icons left">palette</i>
|
||||
Visualize your characters
|
||||
</div>
|
||||
<p>
|
||||
After you've created a character on Notebook.ai, visualizing them is as easy as picking an image style and clicking a button.
|
||||
Everything you've written about what they look like on their notebook page is automatically included.
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Image visualization is a Premium feature, but you can generate up to 100 images for free to try it out for yourself.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if user_signed_in? %>
|
||||
<div class="text-center">
|
||||
<div>Already logged in? Great!</div>
|
||||
<%= link_to 'Visualize your ideas', basil_path, class: 'btn btn-large hoverable blue white-text' %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="text-center">
|
||||
<div>Want to keep visualizing your ideas?</div>
|
||||
<%= link_to 'Get started with Notebook.ai', new_registration_path(User), class: 'btn btn-large hoverable blue white-text' %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% 10.times do %><br /><% end %>
|
||||
@ -20,7 +20,7 @@
|
||||
<% @current_queue_items.each do |commission| %>
|
||||
<li>
|
||||
<%= commission.entity_type %>-<%= commission.entity_id %> (<%= commission.style %>)
|
||||
for U-<%= commission.user_id %>
|
||||
for U-<%= commission.user_id %> (#<%= commission.id %>)
|
||||
</li>
|
||||
<% end %>
|
||||
</ol>
|
||||
@ -29,15 +29,10 @@
|
||||
<% @recent_commissions.each do |commission| %>
|
||||
<div>
|
||||
<% if commission.complete? %>
|
||||
<%# image_tag commission.image, style: 'width: 100%' %>
|
||||
<%
|
||||
s3 = Aws::S3::Resource.new(region: "us-east-1")
|
||||
obj = s3.bucket(commission.s3_bucket).object("job-#{commission.job_id}.png")
|
||||
%>
|
||||
<div class="card horizontal">
|
||||
<div class="card-image">
|
||||
<%= link_to obj.presigned_url(:get) do %>
|
||||
<%= image_tag obj.presigned_url(:get) %>
|
||||
<%= link_to commission.image do %>
|
||||
<%= image_tag commission.image %>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="card-stacked">
|
||||
@ -45,12 +40,17 @@
|
||||
<div>
|
||||
<%= commission.id %>.
|
||||
<% if commission.entity.present? %>
|
||||
<i class="material-icons right <%= content_class_from_name(commission.entity_type).text_color %>"><%= content_class_from_name(commission.entity_type).icon %></i>
|
||||
<strong><%= link_to commission.entity.name, commission.entity %></strong>
|
||||
<% end %>
|
||||
<% if commission.style? %>
|
||||
<em>(<%= commission.style.humanize %>)</em>
|
||||
<% end %>
|
||||
by <%= link_to commission.user.name, commission.user %>
|
||||
<% if commission.user_id %>
|
||||
by <%= link_to commission.user.name, commission.user %>
|
||||
<% else %>
|
||||
anonymous generation
|
||||
<% end %>
|
||||
<div style="font-size: 0.5em"><%= commission.job_id %></div>
|
||||
</div>
|
||||
<ul>
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
</div>
|
||||
<div class="col s12 m8 l9 grey lighten-3">
|
||||
<%=
|
||||
area_chart @commissions.group_by_day(:created_at).map { |date, count| [date.to_date, count] },
|
||||
area_chart @commissions.where('created_at > ?', 30.days.ago.beginning_of_month).group_by_day(:created_at).map { |date, count| [date.to_date, count] },
|
||||
colors: ['#9C27B0', '#2196F3'],
|
||||
title: 'Images created per day',
|
||||
suffix: ' images'
|
||||
|
||||
@ -345,6 +345,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<% if current_user.can_delete?(document) %>
|
||||
<%= link_to document_path(document), method: :delete, class: 'red white-text btn hoverable' do %>
|
||||
<i class="material-icons left">delete</i>
|
||||
Delete
|
||||
<% end %>
|
||||
<% end %>
|
||||
<% if current_user.can_read?(document) %>
|
||||
<%= link_to polymorphic_path(document), class: 'blue white-text text-lighten-1 btn hoverable' do %>
|
||||
<i class="material-icons left"><%= content_type.icon %></i>
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
material for your ideas.
|
||||
</p>
|
||||
<p>
|
||||
To generate an image of this <%= @content.try(:page_type) || 'page' %>,
|
||||
To generate an image of this <%= @content.try(:page_type).try(:downcase) || 'page' %>,
|
||||
simply click a style below. Once the image has generated, you can move it
|
||||
to your <%= link_to @content.try(:name), @content.view_path %> page by clicking "Save" on any image.
|
||||
</p>
|
||||
|
||||
18
app/views/notice_dismissal/messages/_21.html.erb
Normal file
18
app/views/notice_dismissal/messages/_21.html.erb
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="col s12 m5 l4">
|
||||
<div class="grey-text uppercase center">
|
||||
Happening now
|
||||
</div>
|
||||
<%= link_to jam_path do %>
|
||||
<div class="card-panel hoverable pink white-text" style="margin-bottom: 0">
|
||||
<div class="valign-wrapper">
|
||||
<i class="material-icons" class="left" style="font-size: 3em; margin-right: 0.3em;">face_2</i>
|
||||
<div>
|
||||
Join our free Character VizJam event until June 13th!
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<div style="margin-bottom: 2em;">
|
||||
<%= link_to 'dismiss message', notice_dismissal_dismiss_path(notice_id: 21), class: 'right' %>
|
||||
</div>
|
||||
</div>
|
||||
@ -208,6 +208,7 @@ en:
|
||||
- Delicate
|
||||
- Flat
|
||||
- Fragile
|
||||
- Frail
|
||||
- Lean
|
||||
- Lightly muscled
|
||||
- Small-shouldered
|
||||
@ -221,6 +222,9 @@ en:
|
||||
- Big-boned
|
||||
- Round physique
|
||||
- Pear-shaped
|
||||
- Curvy
|
||||
- Overweight
|
||||
- Underweight
|
||||
|
||||
bow_types:
|
||||
- Longbow
|
||||
@ -1859,6 +1863,11 @@ en:
|
||||
- Red
|
||||
- Jet black
|
||||
- Raven black
|
||||
- Orange
|
||||
- Silver
|
||||
- Gold
|
||||
- Yellow
|
||||
- Purple
|
||||
|
||||
sword_types:
|
||||
- Cutlass
|
||||
|
||||
@ -19,6 +19,11 @@ Rails.application.routes.draw do
|
||||
post '/feedback/:jobid', to: 'basil#feedback', as: :basil_feedback
|
||||
post '/:id/save', to: 'basil#save', as: :basil_save
|
||||
delete '/:id/delete', to: 'basil#delete', as: :basil_delete
|
||||
get '/commission/:jobid', to: 'basil#commission_info', as: :basil_commission_info
|
||||
|
||||
# Landing pages
|
||||
get '/jam', to: 'basil#jam', as: :basil_jam
|
||||
post '/jam', to: 'basil#queue_jam_job', as: :basil_jam_submit
|
||||
|
||||
# Standard generation flow for users
|
||||
get '/', to: 'basil#index', as: :basil
|
||||
@ -34,6 +39,9 @@ Rails.application.routes.draw do
|
||||
end
|
||||
end
|
||||
|
||||
# Temporary landing path for jams (nice URL)
|
||||
get '/jam', to: 'basil#jam', as: :jam
|
||||
|
||||
scope :stream, path: '/stream', as: :stream do
|
||||
get '/', to: 'stream#index'
|
||||
get 'world', to: 'stream#global'
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
class DropNotNullConstraintFromBasilCommissions < ActiveRecord::Migration[6.1]
|
||||
def up
|
||||
change_column_null :basil_commissions, :user_id, true
|
||||
change_column_null :basil_commissions, :entity_type, true
|
||||
change_column_null :basil_commissions, :entity_id, true
|
||||
end
|
||||
|
||||
def down
|
||||
change_column_null :basil_commissions, :user_id, false
|
||||
change_column_null :basil_commissions, :entity_type, false
|
||||
change_column_null :basil_commissions, :entity_id, false
|
||||
end
|
||||
end
|
||||
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2023_04_14_183606) do
|
||||
ActiveRecord::Schema.define(version: 2023_05_12_222601) do
|
||||
|
||||
create_table "active_storage_attachments", force: :cascade do |t|
|
||||
t.string "name", null: false
|
||||
@ -187,9 +187,9 @@ ActiveRecord::Schema.define(version: 2023_04_14_183606) do
|
||||
end
|
||||
|
||||
create_table "basil_commissions", force: :cascade do |t|
|
||||
t.integer "user_id", null: false
|
||||
t.string "entity_type", null: false
|
||||
t.integer "entity_id", null: false
|
||||
t.integer "user_id"
|
||||
t.string "entity_type"
|
||||
t.integer "entity_id"
|
||||
t.string "prompt"
|
||||
t.string "job_id"
|
||||
t.datetime "created_at", precision: 6, null: false
|
||||
|
||||
@ -4,6 +4,6 @@ services:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000/tcp"
|
||||
command: sh -c "rake db:migrate && rm -f tmp/pids/server.pid && exec bundle exec rails server -b 0.0.0.0"
|
||||
command: sh -c "rake db:migrate && rm -f tmp/pids/server.pid && exec rails server -b 0.0.0.0"
|
||||
volumes:
|
||||
- "./:/notebook-ai"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user