Skip to content

Commit

Permalink
basic export flow for OpenCharacters
Browse files Browse the repository at this point in the history
  • Loading branch information
drusepth committed Apr 15, 2023
1 parent ff6b64c commit 7111d6b
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 8 deletions.
3 changes: 3 additions & 0 deletions app/assets/javascripts/conversation.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Place all the behaviors and hooks related to the matching controller here.
# All this logic will automatically be available in application.js.
# You can use CoffeeScript in this file: http://coffeescript.org/
3 changes: 3 additions & 0 deletions app/assets/stylesheets/conversation.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Place all the styles related to the Conversation controller here.
// They will automatically be included in application.css.
// You can use Sass (SCSS) here: https://sass-lang.com/
74 changes: 74 additions & 0 deletions app/controllers/conversation_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
class ConversationController < ApplicationController
before_action :set_character
before_action :ensure_character_privacy

def character_landing
@first_greeting = "Hello, friend!"

@personality = personality_for_character
@description = description_for_character
end

def export
raise open_characters_persona_params.inspect
end

private

def personality_for_character
name = @character.name
gender = @character.get_field_value('Overview', 'Gender')
role = @character.get_field_value('Overview', 'Role')
age = @character.get_field_value('Overview', 'Age')
aliases = @character.get_field_value('Overview', 'Aliases')
hobbies = @character.get_field_value('Nature', 'Hobbies')

[
name,
" is a ",
gender.downcase,
" ",
role || "character",
age.present? ? ", #{age}," : nil,
aliases.present? ? "(also known as #{aliases})" : nil,
hobbies.present? ? " into #{hobbies}." : "."
].compact.join
end

def description_for_character
occupation = @character.get_field_value('Social', 'Occupation')
background = @character.get_field_value('History', 'Background')
motivations = @character.get_field_value('Nature', 'Motivations')
mannerisms = @character.get_field_value('Nature', 'Mannerisms')
flaws = @character.get_field_value('Nature', 'Flaws')
prejudices = @character.get_field_value('Nature', 'Prejudices')
talents = @character.get_field_value('Nature', 'Talents')
hobbies = @character.get_field_value('Nature', 'Hobbies')

description_parts = []
description_parts.concat ["OCCUPATION", occupation, nil] if occupation.present?
description_parts.concat ["BACKGROUND", background, nil] if background.present?
description_parts.concat ["MOTIVATIONS", motivations, nil] if motivations.present?
description_parts.concat ["MANNERISMS", mannerisms, nil] if mannerisms.present?
description_parts.concat ["FLAWS", flaws, nil] if flaws.present?
description_parts.concat ["PREJUDICES", prejudices, nil] if prejudices.present?
description_parts.concat ["TALENTS", talents, nil] if talents.present?
description_parts.concat ["HOBBIES", hobbies, nil] if hobbies.present?

description_parts.join("\n")
end

def set_character
@character = Character.find(params[:character_id].to_i)
end

def ensure_character_privacy
unless (user_signed_in? && @character.user == current_user) || @character.privacy == 'public'
redirect_to root_path, notice: "That character is private!"
end
end

def open_characters_persona_params
params.permit(:name, :avatar, :scenario, :char_greeting, :personality, :description, :example_dialogue)
end
end
2 changes: 2 additions & 0 deletions app/helpers/conversation_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
module ConversationHelper
end
28 changes: 28 additions & 0 deletions app/models/concerns/has_attributes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,34 @@ def overview_field_value(label)
.detect { |v| v.entity_id == self.id }&.value.presence || (self.respond_to?(label.downcase) ? self.read_attribute(label.downcase) : nil)
end

def get_field_value(category, field)
category = AttributeCategory.find_by(
label: category,
entity_type: self.class.name.downcase,
user_id: self.user_id,
hidden: [nil, false]
)
return nil if category.nil?

field = AttributeField.find_by(
label: field,
attribute_category_id: category.id,
user_id: self.user_id,
hidden: [nil, false]
)
return nil if field.nil?

answer = Attribute.find_by(
attribute_field_id: field.id,
entity_type: self.class.name,
entity_id: self.id,
user_id: self.user_id
)
return nil if answer.nil?

answer.value
end

def self.field_type_for(category, field)
if field[:label] == 'Name' && category.name == 'overview'
"name"
Expand Down
30 changes: 22 additions & 8 deletions app/views/content/display/sidebar/_apps.html.erb
Original file line number Diff line number Diff line change
@@ -1,20 +1,34 @@
<%
creating = defined?(creating) && creating
editing = defined?(editing) && editing
page_type_enabled = BasilService::ENABLED_PAGE_TYPES.include? content.class_name
show_basil_tool = BasilService::ENABLED_PAGE_TYPES.include? content.class_name
show_conversation = content.class_name == 'Character'

show_tools_menu = show_basil_tool || show_conversation
%>

<% if page_type_enabled %>
<% if show_tools_menu %>
<ul class="collection content-tabs">
<li class="active center grey-text uppercase">
Tools
</li>

<li class="collection-item">
<%= link_to basil_content_path(content_type: content.class_name, id: content.id) do %>
<i class="material-icons left">palette</i>
Image Generation
<% end %>
</li>
<% if show_basil_tool %>
<li class="collection-item">
<%= link_to basil_content_path(content_type: content.class_name, id: content.id) do %>
<i class="material-icons left">palette</i>
Image Generation
<% end %>
</li>
<% end %>

<% if show_conversation %>
<li class="collection-item">
<%= link_to talk_path(character_id: content.id) do %>
<i class="material-icons left">message</i>
Talk to <%= content.name %>
<% end %>
</li>
<% end %>
</ul>
<% end %>
89 changes: 89 additions & 0 deletions app/views/conversation/character_landing.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<div class="row">
<div class="col s12 m3">
<%= image_tag @character.random_image_including_private %>

<%= link_to "Back to #{@character.name}", @character %>
</div>
<div class="col s12 m9">
<h1 style="font-size: 1.4em">
<strong>Talk to <%= @character.name %></strong>
</h1>
<p>
You can now export your Notebook.ai characters to the open-source project OpenCharacters
and talk to them in real-time! This is a great way to get to know your characters a little
better or to roleplay with them.
</p>
<p>
The fields below will be shared with OpenCharacters to create a conversational persona of
your character. You can edit these fields before exporting if you want to change how your
character talks. If your character is public, you can also share this page with others
to let them talk to your character!
</p>

<br />


<%= form_tag export_character_path(@character.id), method: :post do |f| %>

<%= hidden_field_tag "name", @character.name %>
<%= hidden_field_tag "avatar", @character.random_image_including_private %>
<%# TODO background image/music/etc? %>

<div class="card-panel">
<p class="center">
<strong>Persona export for <%= @character.name %></strong>
<% if user_signed_in? && @character.user_id == current_user.id %>
<br />
<em>(editable because you created <%= @character.name %>)</em>
<% end %>
</p>
<br /><br />

<div class="input-field">
<%= text_area_tag 'scenario', nil, disabled: false, style: 'min-height: 100px', placeholder: "Is there a specific scenario/context you want to have this conversation in?", class: 'materialize-textarea' %>
<label for="scenario">Optional: Scenario</label>
</div>

<div class="input-field">
<%= text_area_tag 'char_greeting', @first_greeting, disabled: false, placeholder: "This will be the first thing your character says to you. It can be a simple greeting, or you can use it to set a specific topic, tone, or speaking style.", class: 'materialize-textarea' %>
<label for="char_greeting">First greeting from <%= @character.name %></label>
</div>

<div class="input-field">
<%= text_area_tag 'personality', @personality, disabled: false, class: 'materialize-textarea' %>
<label for="personality">Personality</label>
</div>

<div class="input-field">
<%= text_area_tag 'description', @description, disabled: false, class: 'materialize-textarea' %>
<label for="description">Description</label>
</div>

<div class="input-field">
<%= text_area_tag 'example_dialogue', nil, disabled: false, style: 'min-height: 100px', placeholder: "If you have any dialogue examples, quotes, or other phrases your character says, you can use this field to include them and adjust their speaking style closer to the examples. Write as little or as much as you'd like!", class: 'materialize-textarea' %>
<label for="example_dialogue">Optional: More dialogue examples</label>
</div>
</div>

<div class="card-panel">
<div class="center">
<strong>Note: OpenAI key required by OpenCharacters</strong>
</div>

<p>
OpenCharacters uses this persona data with OpenAI's GPT models
in an app that runs entirely in your browser, rather than being hosted or stored on any server.
This means that you will need a valid OpenAI key to use this feature.
</p>
</div>

<br />
<div class="center">
<%= submit_tag "Chat with #{@character.name}", class: 'hoverable btn blue white-text' %>
<span class="grey-text text-darken-1" style="margin-left: 1rem">using OpenCharacters</span>
</div>

<% 10.times do %><br /><% end %>
<% end %>
</div>
</div>
5 changes: 5 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
get '/:content_type/:id', to: 'basil#content', as: :basil_content
post '/:content_type/:id', to: 'basil#commission', as: :basil_commission
end

scope :talk do
get '/to/:character_id', to: 'conversation#character_landing', as: :talk
post '/export/:character_id', to: 'conversation#export', as: :export_character
end
end

scope :stream, path: '/stream', as: :stream do
Expand Down
8 changes: 8 additions & 0 deletions test/controllers/conversation_controller_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
require "test_helper"

class ConversationControllerTest < ActionDispatch::IntegrationTest
test "should get content" do
get conversation_content_url
assert_response :success
end
end

0 comments on commit 7111d6b

Please sign in to comment.