Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for more field types #51

Merged
merged 4 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion app/dashboards/form_field_dashboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,16 @@ class FormFieldDashboard < Administrate::BaseDashboard
ATTRIBUTE_TYPES = {
id: Field::Number,
description: Field::String,
friendly_name: Field::String,
field_type: Field::Select.with_options(
collection: FormField.field_types.keys.map { |t| [ t.humanize, t ] }
),
metadata: Field::String.with_options(searchable: false),
minimum: Field::String,
maximum: Field::String,
enum_options: Field::StringArray.with_options(
hint: "Enter options for single/multi select fields, one per line"
),
page: Field::BelongsTo,
title: Field::String,
created_at: Field::DateTime,
Expand All @@ -25,6 +34,8 @@ class FormFieldDashboard < Administrate::BaseDashboard
COLLECTION_ATTRIBUTES = %i[
id
title
friendly_name
field_type
description
page
].freeze
Expand All @@ -34,9 +45,14 @@ class FormFieldDashboard < Administrate::BaseDashboard
SHOW_PAGE_ATTRIBUTES = %i[
id
title
friendly_name
field_type
description
page
metadata
minimum
maximum
enum_options
created_at
updated_at
].freeze
Expand All @@ -45,10 +61,15 @@ class FormFieldDashboard < Administrate::BaseDashboard
# an array of attributes that will be displayed
# on the model's form (`new` and `edit`) pages.
FORM_ATTRIBUTES = %i[
page
friendly_name
title
description
page
metadata
field_type
minimum
maximum
enum_options
].freeze

# COLLECTION_FILTERS
Expand Down
16 changes: 7 additions & 9 deletions app/helpers/openai_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def ai_transcribe(file)
def system_prompt(transcription)
return transcription.page.prompt if transcription.page.prompt.present?

"You are an AI assitant filling the form for an user. Make sure that you do not populate the form with any data that the user did not provide. Ensure all data shared by users are correctly split into function arguments."
"You are an AI assistant filling the form for a user. Make sure that you do not populate the form with any data that the user did not provide. Ensure all data shared by users are correctly split into function arguments."
end

def ai_generate_completion(transcription)
Expand Down Expand Up @@ -71,11 +71,10 @@ def ai_generate_completion(transcription)
end

def smart_description(form_field, context)
if context[form_field.title]
"#{form_field.description}; Context: #{context[form_field.title]}"
else
form_field.description
end
base_description = form_field.description.presence || form_field.friendly_name
context_info = context[form_field.title] if context.present?

[ base_description, context_info ].compact.join("; Context: ")
end

def usage_tokens(transcription, usage)
Expand All @@ -89,10 +88,9 @@ def usage_tokens(transcription, usage)
def create_types_form_form_fields(form_fields, context)
fields = {}
form_fields.each do |form_field|
fields[form_field.title] = {
type: :string,
fields[form_field.title] = form_field.to_json_schema_for_ai.merge(
description: smart_description(form_field, context)
}
)
end
fields
end
Expand Down
81 changes: 81 additions & 0 deletions app/models/form_field.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,84 @@
class FormField < ApplicationRecord
belongs_to :page

# Using string enum for better readability
enum field_type: {
string: "string", # Text input
number: "number", # Numeric input
boolean: "boolean", # Yes/No values
single_select: "single_select", # Single choice from options
multi_select: "multi_select" # Multiple choices from options
}

validates :title, presence: true
validates :friendly_name, presence: true
validates :field_type, presence: true
validate :validate_select_options
validate :validate_number_constraints

# Virtual attribute for enum options
attr_writer :enum_options_raw

def enum_options_raw
@enum_options_raw || enum_options&.join("\n")
end

before_validation :process_enum_options

def to_json_schema_for_ai
schema = {
type: json_schema_type,
description: description
}

case field_type
when "single_select", "multi_select"
schema[:enum] = enum_options if enum_options.present?
when "number"
schema[:minimum] = minimum if minimum.present?
schema[:maximum] = maximum if maximum.present?
end

schema.compact
end

private

def json_schema_type
case field_type
when "single_select"
"string"
when "multi_select"
"array"
else
field_type
end
end

def validate_select_options
return unless ["single_select", "multi_select"].include?(field_type)

Check failure on line 59 in app/models/form_field.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

Check failure on line 59 in app/models/form_field.rb

View workflow job for this annotation

GitHub Actions / lint

Layout/SpaceInsideArrayLiteralBrackets: Use space inside array brackets.

if enum_options.blank? || enum_options.any?(&:blank?)
errors.add(:enum_options, "must have at least one non-empty option for select fields")
end
end

def validate_number_constraints
return unless field_type == "number"

if minimum.present? && maximum.present? && maximum.to_f < minimum.to_f
errors.add(:maximum, "must be greater than minimum")
end
end

def process_enum_options
return unless @enum_options_raw.present?

# Split by newlines, remove empty lines and whitespace
self.enum_options = @enum_options_raw
.split("\n")
.map(&:strip)
.reject(&:blank?)
.uniq
end
end
15 changes: 15 additions & 0 deletions app/views/fields/string_array/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<div class="field-unit__label">
<%= f.label field.attribute %>
</div>
<div class="field-unit__field">
<%= f.text_area "#{field.attribute}_raw",
value: field.data&.join("\n"),
class: "string-array-input",
rows: 5,
placeholder: "Enter one option per line" %>
<% if field.options.key?(:hint) %>
<p class="field-unit__hint">
<%= field.options[:hint] %>
</p>
<% end %>
</div>
1 change: 1 addition & 0 deletions app/views/fields/string_array/_index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= field.to_s %>
5 changes: 5 additions & 0 deletions app/views/fields/string_array/_show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<% field.data.map do |item| %>
<div>
- <%= item %>
</div>
<% end %>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add nil check to prevent potential errors

The current implementation might raise an error if field.data is nil.

Apply this diff to add nil protection:

-<% field.data.map do |item| %>
+<% if field.data.present? %>
+  <ul class="string-array-list">
+    <% field.data.each do |item| %>
+      <li><%= item %></li>
+    <% end %>
+  </ul>
+<% end %>
-  <div>
-    - <%= item %>
-  </div>
-<% end %>

This change:

  1. Adds nil protection with present? check
  2. Uses semantic <ul> and <li> tags for list structure
  3. Replaces map with each since we're not collecting results
  4. Adds a CSS class for styling
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<% field.data.map do |item| %>
<div>
- <%= item %>
</div>
<% end %>
<% if field.data.present? %>
<ul class="string-array-list">
<% field.data.each do |item| %>
<li><%= item %></li>
<% end %>
</ul>
<% end %>

21 changes: 21 additions & 0 deletions db/migrate/20241113130440_add_validation_fields_to_form_fields.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
class AddValidationFieldsToFormFields < ActiveRecord::Migration[8.0]
def change
add_column :form_fields, :friendly_name, :string
add_column :form_fields, :field_type, :string, null: false, default: 'string'

# For number fields
add_column :form_fields, :minimum, :string
add_column :form_fields, :maximum, :string
bodhish marked this conversation as resolved.
Show resolved Hide resolved

# For select fields
add_column :form_fields, :enum_options, :string, array: true, default: []

# Copy existing title values to friendly_name
FormField.find_each do |field|
field.update_column(:friendly_name, field.title)
end
bodhish marked this conversation as resolved.
Show resolved Hide resolved

# Make friendly_name required after copying data
change_column_null :form_fields, :friendly_name, false
end
end
7 changes: 6 additions & 1 deletion db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading