Skip to content

Commit 7111d6b

Browse files
committed
basic export flow for OpenCharacters
1 parent ff6b64c commit 7111d6b

File tree

9 files changed

+234
-8
lines changed

9 files changed

+234
-8
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Place all the behaviors and hooks related to the matching controller here.
2+
# All this logic will automatically be available in application.js.
3+
# You can use CoffeeScript in this file: http://coffeescript.org/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Place all the styles related to the Conversation controller here.
2+
// They will automatically be included in application.css.
3+
// You can use Sass (SCSS) here: https://sass-lang.com/
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
class ConversationController < ApplicationController
2+
before_action :set_character
3+
before_action :ensure_character_privacy
4+
5+
def character_landing
6+
@first_greeting = "Hello, friend!"
7+
8+
@personality = personality_for_character
9+
@description = description_for_character
10+
end
11+
12+
def export
13+
raise open_characters_persona_params.inspect
14+
end
15+
16+
private
17+
18+
def personality_for_character
19+
name = @character.name
20+
gender = @character.get_field_value('Overview', 'Gender')
21+
role = @character.get_field_value('Overview', 'Role')
22+
age = @character.get_field_value('Overview', 'Age')
23+
aliases = @character.get_field_value('Overview', 'Aliases')
24+
hobbies = @character.get_field_value('Nature', 'Hobbies')
25+
26+
[
27+
name,
28+
" is a ",
29+
gender.downcase,
30+
" ",
31+
role || "character",
32+
age.present? ? ", #{age}," : nil,
33+
aliases.present? ? "(also known as #{aliases})" : nil,
34+
hobbies.present? ? " into #{hobbies}." : "."
35+
].compact.join
36+
end
37+
38+
def description_for_character
39+
occupation = @character.get_field_value('Social', 'Occupation')
40+
background = @character.get_field_value('History', 'Background')
41+
motivations = @character.get_field_value('Nature', 'Motivations')
42+
mannerisms = @character.get_field_value('Nature', 'Mannerisms')
43+
flaws = @character.get_field_value('Nature', 'Flaws')
44+
prejudices = @character.get_field_value('Nature', 'Prejudices')
45+
talents = @character.get_field_value('Nature', 'Talents')
46+
hobbies = @character.get_field_value('Nature', 'Hobbies')
47+
48+
description_parts = []
49+
description_parts.concat ["OCCUPATION", occupation, nil] if occupation.present?
50+
description_parts.concat ["BACKGROUND", background, nil] if background.present?
51+
description_parts.concat ["MOTIVATIONS", motivations, nil] if motivations.present?
52+
description_parts.concat ["MANNERISMS", mannerisms, nil] if mannerisms.present?
53+
description_parts.concat ["FLAWS", flaws, nil] if flaws.present?
54+
description_parts.concat ["PREJUDICES", prejudices, nil] if prejudices.present?
55+
description_parts.concat ["TALENTS", talents, nil] if talents.present?
56+
description_parts.concat ["HOBBIES", hobbies, nil] if hobbies.present?
57+
58+
description_parts.join("\n")
59+
end
60+
61+
def set_character
62+
@character = Character.find(params[:character_id].to_i)
63+
end
64+
65+
def ensure_character_privacy
66+
unless (user_signed_in? && @character.user == current_user) || @character.privacy == 'public'
67+
redirect_to root_path, notice: "That character is private!"
68+
end
69+
end
70+
71+
def open_characters_persona_params
72+
params.permit(:name, :avatar, :scenario, :char_greeting, :personality, :description, :example_dialogue)
73+
end
74+
end

app/helpers/conversation_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
module ConversationHelper
2+
end

app/models/concerns/has_attributes.rb

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,34 @@ def overview_field_value(label)
300300
.detect { |v| v.entity_id == self.id }&.value.presence || (self.respond_to?(label.downcase) ? self.read_attribute(label.downcase) : nil)
301301
end
302302

303+
def get_field_value(category, field)
304+
category = AttributeCategory.find_by(
305+
label: category,
306+
entity_type: self.class.name.downcase,
307+
user_id: self.user_id,
308+
hidden: [nil, false]
309+
)
310+
return nil if category.nil?
311+
312+
field = AttributeField.find_by(
313+
label: field,
314+
attribute_category_id: category.id,
315+
user_id: self.user_id,
316+
hidden: [nil, false]
317+
)
318+
return nil if field.nil?
319+
320+
answer = Attribute.find_by(
321+
attribute_field_id: field.id,
322+
entity_type: self.class.name,
323+
entity_id: self.id,
324+
user_id: self.user_id
325+
)
326+
return nil if answer.nil?
327+
328+
answer.value
329+
end
330+
303331
def self.field_type_for(category, field)
304332
if field[:label] == 'Name' && category.name == 'overview'
305333
"name"
Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,34 @@
11
<%
22
creating = defined?(creating) && creating
33
editing = defined?(editing) && editing
4-
page_type_enabled = BasilService::ENABLED_PAGE_TYPES.include? content.class_name
4+
show_basil_tool = BasilService::ENABLED_PAGE_TYPES.include? content.class_name
5+
show_conversation = content.class_name == 'Character'
6+
7+
show_tools_menu = show_basil_tool || show_conversation
58
%>
69

7-
<% if page_type_enabled %>
10+
<% if show_tools_menu %>
811
<ul class="collection content-tabs">
912
<li class="active center grey-text uppercase">
1013
Tools
1114
</li>
1215

13-
<li class="collection-item">
14-
<%= link_to basil_content_path(content_type: content.class_name, id: content.id) do %>
15-
<i class="material-icons left">palette</i>
16-
Image Generation
17-
<% end %>
18-
</li>
16+
<% if show_basil_tool %>
17+
<li class="collection-item">
18+
<%= link_to basil_content_path(content_type: content.class_name, id: content.id) do %>
19+
<i class="material-icons left">palette</i>
20+
Image Generation
21+
<% end %>
22+
</li>
23+
<% end %>
24+
25+
<% if show_conversation %>
26+
<li class="collection-item">
27+
<%= link_to talk_path(character_id: content.id) do %>
28+
<i class="material-icons left">message</i>
29+
Talk to <%= content.name %>
30+
<% end %>
31+
</li>
32+
<% end %>
1933
</ul>
2034
<% end %>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<div class="row">
2+
<div class="col s12 m3">
3+
<%= image_tag @character.random_image_including_private %>
4+
5+
<%= link_to "Back to #{@character.name}", @character %>
6+
</div>
7+
<div class="col s12 m9">
8+
<h1 style="font-size: 1.4em">
9+
<strong>Talk to <%= @character.name %></strong>
10+
</h1>
11+
<p>
12+
You can now export your Notebook.ai characters to the open-source project OpenCharacters
13+
and talk to them in real-time! This is a great way to get to know your characters a little
14+
better or to roleplay with them.
15+
</p>
16+
<p>
17+
The fields below will be shared with OpenCharacters to create a conversational persona of
18+
your character. You can edit these fields before exporting if you want to change how your
19+
character talks. If your character is public, you can also share this page with others
20+
to let them talk to your character!
21+
</p>
22+
23+
<br />
24+
25+
26+
<%= form_tag export_character_path(@character.id), method: :post do |f| %>
27+
28+
<%= hidden_field_tag "name", @character.name %>
29+
<%= hidden_field_tag "avatar", @character.random_image_including_private %>
30+
<%# TODO background image/music/etc? %>
31+
32+
<div class="card-panel">
33+
<p class="center">
34+
<strong>Persona export for <%= @character.name %></strong>
35+
<% if user_signed_in? && @character.user_id == current_user.id %>
36+
<br />
37+
<em>(editable because you created <%= @character.name %>)</em>
38+
<% end %>
39+
</p>
40+
<br /><br />
41+
42+
<div class="input-field">
43+
<%= 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' %>
44+
<label for="scenario">Optional: Scenario</label>
45+
</div>
46+
47+
<div class="input-field">
48+
<%= 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' %>
49+
<label for="char_greeting">First greeting from <%= @character.name %></label>
50+
</div>
51+
52+
<div class="input-field">
53+
<%= text_area_tag 'personality', @personality, disabled: false, class: 'materialize-textarea' %>
54+
<label for="personality">Personality</label>
55+
</div>
56+
57+
<div class="input-field">
58+
<%= text_area_tag 'description', @description, disabled: false, class: 'materialize-textarea' %>
59+
<label for="description">Description</label>
60+
</div>
61+
62+
<div class="input-field">
63+
<%= 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' %>
64+
<label for="example_dialogue">Optional: More dialogue examples</label>
65+
</div>
66+
</div>
67+
68+
<div class="card-panel">
69+
<div class="center">
70+
<strong>Note: OpenAI key required by OpenCharacters</strong>
71+
</div>
72+
73+
<p>
74+
OpenCharacters uses this persona data with OpenAI's GPT models
75+
in an app that runs entirely in your browser, rather than being hosted or stored on any server.
76+
This means that you will need a valid OpenAI key to use this feature.
77+
</p>
78+
</div>
79+
80+
<br />
81+
<div class="center">
82+
<%= submit_tag "Chat with #{@character.name}", class: 'hoverable btn blue white-text' %>
83+
<span class="grey-text text-darken-1" style="margin-left: 1rem">using OpenCharacters</span>
84+
</div>
85+
86+
<% 10.times do %><br /><% end %>
87+
<% end %>
88+
</div>
89+
</div>

config/routes.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@
2525
get '/:content_type/:id', to: 'basil#content', as: :basil_content
2626
post '/:content_type/:id', to: 'basil#commission', as: :basil_commission
2727
end
28+
29+
scope :talk do
30+
get '/to/:character_id', to: 'conversation#character_landing', as: :talk
31+
post '/export/:character_id', to: 'conversation#export', as: :export_character
32+
end
2833
end
2934

3035
scope :stream, path: '/stream', as: :stream do
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require "test_helper"
2+
3+
class ConversationControllerTest < ActionDispatch::IntegrationTest
4+
test "should get content" do
5+
get conversation_content_url
6+
assert_response :success
7+
end
8+
end

0 commit comments

Comments
 (0)