ruby_conversations 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +125 -0
  3. data/Rakefile +12 -0
  4. data/app/models/concerns/ruby_conversations/messageable.rb +13 -0
  5. data/app/models/concerns/ruby_conversations/versionable.rb +20 -0
  6. data/app/models/ruby_conversations/ai_conversation.rb +107 -0
  7. data/app/models/ruby_conversations/ai_message.rb +28 -0
  8. data/app/models/ruby_conversations/ai_message_input.rb +22 -0
  9. data/app/models/ruby_conversations/ai_message_prompt.rb +28 -0
  10. data/app/models/ruby_conversations/message_builder.rb +100 -0
  11. data/app/models/ruby_conversations/prompt.rb +104 -0
  12. data/app/models/ruby_conversations/prompt_version.rb +16 -0
  13. data/lib/generators/ruby_conversations/install/install_generator.rb +60 -0
  14. data/lib/generators/ruby_conversations/install/templates/README.md +42 -0
  15. data/lib/generators/ruby_conversations/install/templates/initializer.rb.erb +18 -0
  16. data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_conversations.rb.erb +13 -0
  17. data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_message_inputs.rb.erb +13 -0
  18. data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_message_prompts.rb.erb +18 -0
  19. data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_messages.rb.erb +18 -0
  20. data/lib/generators/ruby_conversations/install/templates/migrations/create_ai_tool_calls.rb.erb +15 -0
  21. data/lib/generators/ruby_conversations/install/templates/migrations/create_prompt_versions.rb.erb +14 -0
  22. data/lib/generators/ruby_conversations/install/templates/migrations/create_prompts.rb.erb +16 -0
  23. data/lib/ruby_conversations/configuration.rb +28 -0
  24. data/lib/ruby_conversations/engine.rb +41 -0
  25. data/lib/ruby_conversations/jwt_client.rb +23 -0
  26. data/lib/ruby_conversations/storage/base.rb +28 -0
  27. data/lib/ruby_conversations/storage/local.rb +30 -0
  28. data/lib/ruby_conversations/storage/remote.rb +93 -0
  29. data/lib/ruby_conversations/version.rb +9 -0
  30. data/lib/ruby_conversations.rb +62 -0
  31. metadata +314 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 29d0a2bc2bf832a2bd4a452e1213d024604d73c3be3406851d2ac77dce20278d
4
+ data.tar.gz: 973a5a996c8146aa0651dfeb42cb95d27c9a4623c53b1b4f662cefb48f90bc88
5
+ SHA512:
6
+ metadata.gz: bf8a698597f4a53c26a0850ee8afc0552d9280b5ea46ba05b890e5bebaa587e40d666e93349ec1667e1ac438c881b9a1afa79b7482cc970fedc49d7e89d49be5
7
+ data.tar.gz: c9b77726ec303b37ad0af8f8e16ad3979802f8d995c254dcd2713a4a60563e0cbd7865b4bb433ade118ff2a597c8cc896940004b0abc874b802c63f666d037ba
data/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # RubyConversations
2
+
3
+ A Rails engine for managing AI conversations with support for both local and remote storage modes. Built on top of RubyLLM for AI interactions.
4
+
5
+ ## Features
6
+
7
+ - **Flexible Storage**: Choose between local database storage or remote API storage
8
+ - **Built-in Prompt Management**: Version-controlled prompts with placeholder validation
9
+ - **Conversation History**: Track and manage conversation threads
10
+ - **Input/Output Storage**: Structured storage for message inputs and responses
11
+ - **Real-time Updates**: Built-in broadcasting support for real-time applications
12
+ - **JWT Authentication**: Secure remote mode with JWT authentication
13
+ - **Easy Integration**: Simple setup with Rails generators
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem 'ruby_conversations'
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ ```bash
26
+ bundle install
27
+ ```
28
+
29
+ Or install it yourself as:
30
+
31
+ ```bash
32
+ gem install ruby_conversations
33
+ ```
34
+
35
+ After installation, run the setup generator:
36
+
37
+ ```bash
38
+ rails generate ai_conversation_engine:install
39
+ ```
40
+
41
+ This will:
42
+ - Create necessary database migrations
43
+ - Generate an initializer file
44
+ - Set up default configurations
45
+
46
+ ## Configuration
47
+
48
+ Configure the engine in `config/initializers/ai_conversation_engine.rb`:
49
+
50
+ ```ruby
51
+ AiConversationEngine.configure do |config|
52
+ # Storage mode: :local or :remote
53
+ config.storage_mode = :local
54
+
55
+ # Remote API settings (only needed for remote mode)
56
+ # config.api_url = ENV['AI_CONVERSATION_API_URL']
57
+ # config.jwt_secret = ENV['AI_CONVERSATION_JWT_SECRET']
58
+
59
+ # Default LLM settings
60
+ config.default_llm_model = 'gpt-4'
61
+
62
+ # Customize model behaviors
63
+ config.conversation_class = 'AiConversation'
64
+ config.message_class = 'AiMessage'
65
+ config.prompt_class = 'Prompt'
66
+
67
+ end
68
+ ```
69
+
70
+ ## Usage
71
+
72
+ ### Basic Conversation
73
+
74
+ ```ruby
75
+ # Create a new conversation
76
+ conversation = AiConversation.create!
77
+
78
+ # Ask a question using a predefined prompt
79
+ conversation.ask("explain_code", inputs: { code: "def hello; end" })
80
+ ```
81
+
82
+ ### With Associated Objects
83
+
84
+ ```ruby
85
+ # Create a conversation linked to another object
86
+ class Project < ApplicationRecord
87
+ has_many :conversations, as: :conversationable,
88
+ class_name: 'AiConversationEngine::AiConversation'
89
+ end
90
+
91
+ project = Project.find(1)
92
+ conversation = project.conversations.create!
93
+ conversation.ask("analyze_project", inputs: { name: project.name })
94
+ ```
95
+
96
+ ### Remote Storage Mode
97
+
98
+ ```ruby
99
+ # Configure for remote storage
100
+ AiConversationEngine.configure do |config|
101
+ config.storage_mode = :remote
102
+ config.api_url = "https://ai-conversation-api.example.com"
103
+ config.jwt_secret = ENV['JWT_SECRET']
104
+ end
105
+
106
+ # Usage remains the same
107
+ conversation = AiConversation.create!
108
+ conversation.ask("explain_code", inputs: { code: "def hello; end" })
109
+ ```
110
+
111
+ ### Real-time Updates
112
+
113
+ ```ruby
114
+ # In your view
115
+ <%= turbo_stream_from "chat_#{@conversation.id}" %>
116
+
117
+ # Updates are automatically broadcast when messages are added
118
+ ```
119
+
120
+ ## Development
121
+
122
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
123
+
124
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
125
+
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Provides message-related functionalities for conversations.
5
+ module Messageable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :ai_conversations, as: :conversationable, dependent: :destroy
10
+ has_many :ai_messages, through: :ai_conversations
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Provides versioning functionalities for models.
5
+ module Versionable
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :versions, class_name: 'PromptVersion', dependent: :destroy
10
+ has_one :latest_version, -> { order(version: :desc) }, class_name: 'PromptVersion'
11
+
12
+ delegate :content, :metadata, to: :latest_version, allow_nil: true
13
+ end
14
+
15
+ def create_version!(content:, metadata: {})
16
+ next_version = versions.maximum(:version).to_i + 1
17
+ versions.create!(content: content, metadata: metadata, version: next_version)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # An AI conversation is a conversation between an AI and a user.
5
+ class AiConversation < ActiveRecord::Base
6
+ # Associations
7
+ belongs_to :conversationable, polymorphic: true, optional: true, touch: true
8
+ has_many :ai_messages, class_name: RubyConversations.configuration.message_class, dependent: :destroy
9
+
10
+ # Validations
11
+ validates :ai_messages, presence: { message: 'At least one message is required' }, on: :update
12
+ validate :validate_messages
13
+
14
+ # Scopes
15
+ scope :recent, -> { order(updated_at: :desc) }
16
+ scope :with_messages, -> { includes(:ai_messages) }
17
+
18
+ # Attributes
19
+ attr_accessor :model_identifier, :provider, :tool
20
+
21
+ def initialize(attributes = nil)
22
+ attributes ||= {}
23
+ super
24
+ @model_identifier ||= attributes[:model_identifier] || RubyConversations.configuration.default_llm_model
25
+ @provider ||= attributes[:provider] || RubyConversations.configuration.default_llm_provider
26
+ end
27
+
28
+ # Instance Methods
29
+ def ask_multiple(prompt_inputs, description: nil)
30
+ MessageBuilder.new(self).build_from_multiple_prompts(prompt_inputs, description: description)
31
+ self
32
+ end
33
+
34
+ def ask(name, description: nil, inputs: {})
35
+ MessageBuilder.new(self).build_from_single_prompt(name, description: description, inputs: inputs)
36
+ self
37
+ end
38
+
39
+ def execute(system_message: nil)
40
+ save!
41
+ message = ai_messages.last
42
+ raise ArgumentError, 'Conversation must have at least one message to execute' unless message
43
+
44
+ chat = setup_llm_chat(system_message: system_message)
45
+ response = chat.ask(message.request)
46
+ update_last_message_response(message, response)
47
+ response
48
+ end
49
+
50
+ def latest_message
51
+ ai_messages.order(created_at: :desc).first
52
+ end
53
+
54
+ def message_count
55
+ ai_messages.count
56
+ end
57
+
58
+ private
59
+
60
+ def validate_messages
61
+ ai_messages.each do |message|
62
+ validate_message_level(message)
63
+ message.ai_message_prompts.each do |prompt|
64
+ validate_prompt_level(prompt)
65
+ prompt.ai_message_inputs.each do |input|
66
+ validate_input_level(input)
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ def validate_message_level(message)
73
+ return if message.valid?
74
+
75
+ message.errors.full_messages.each do |msg|
76
+ errors.add(:base, "Message error: #{msg}")
77
+ end
78
+ end
79
+
80
+ def validate_prompt_level(prompt)
81
+ return if prompt.valid?
82
+
83
+ prompt.errors.full_messages.each do |msg|
84
+ errors.add(:base, "Message prompt error: #{msg}")
85
+ end
86
+ end
87
+
88
+ def validate_input_level(input)
89
+ return if input.valid?
90
+
91
+ input.errors.full_messages.each do |msg|
92
+ errors.add(:base, "Message prompt input error: #{msg}")
93
+ end
94
+ end
95
+
96
+ def setup_llm_chat(system_message: nil)
97
+ chat = RubyLLM.chat(model: model_identifier, provider: provider).with_temperature(0.0)
98
+ chat.with_tool(tool) if tool.present?
99
+ chat.add_message(role: :system, content: system_message) if system_message.present?
100
+ chat
101
+ end
102
+
103
+ def update_last_message_response(message, response)
104
+ message.update!(response: response.to_h.to_json)
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # An AI message is a message in an AI conversation.
5
+ class AiMessage < ActiveRecord::Base
6
+ # Associations
7
+ belongs_to :ai_conversation, class_name: RubyConversations.configuration.conversation_class
8
+ has_many :ai_message_prompts, class_name: RubyConversations.configuration.message_prompt_class, dependent: :destroy
9
+ has_many :prompts, through: :ai_message_prompts, class_name: RubyConversations.configuration.prompt_class
10
+
11
+ # Validations
12
+ validates :llm, presence: true
13
+ validates :request, presence: true
14
+
15
+ # Scopes
16
+ scope :ordered, -> { order(created_at: :desc) }
17
+ scope :with_tool, ->(tool) { where(tool: tool) }
18
+ scope :with_prompts, -> { includes(:ai_message_prompts) }
19
+
20
+ def prompt_inputs
21
+ ai_message_prompts.includes(:ai_message_inputs).flat_map(&:ai_message_inputs)
22
+ end
23
+
24
+ def tool?
25
+ tool.present?
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Represents an input provided for a specific prompt placeholder within an AI message.
5
+ class AiMessageInput < ActiveRecord::Base
6
+ # Associations
7
+ belongs_to :ai_message_prompt
8
+
9
+ # Validations
10
+ validates :placeholder_name, presence: true
11
+ validate :value_not_nil
12
+
13
+ # Scopes
14
+ scope :ordered, -> { order(:placeholder_name) }
15
+
16
+ private
17
+
18
+ def value_not_nil
19
+ errors.add(:value, "can't be blank") if value.nil?
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Represents the association between an AI message and a specific version of a Prompt.
5
+ class AiMessagePrompt < ActiveRecord::Base
6
+ # Associations
7
+ belongs_to :ai_message
8
+ belongs_to :prompt, optional: true
9
+ belongs_to :prompt_version, optional: true
10
+ has_many :ai_message_inputs, class_name: RubyConversations.configuration.message_input_class, dependent: :destroy
11
+
12
+ # Validations
13
+ validate :prompt_or_inline_data_present
14
+
15
+ # Scopes
16
+ scope :with_inputs, -> { includes(:ai_message_inputs) }
17
+ scope :for_prompt, ->(prompt) { where(prompt: prompt) }
18
+
19
+ private
20
+
21
+ def prompt_or_inline_data_present
22
+ return if prompt_id.present? || prompt_version_id.present?
23
+ return if name.present? && role.present? && draft.present?
24
+
25
+ errors.add(:base, 'Must have either a prompt/version reference or inline prompt data (name, role, draft)')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Builds AiMessage objects with their prompts and inputs based on Prompt templates.
5
+ class MessageBuilder
6
+ def initialize(conversation)
7
+ @conversation = conversation
8
+ end
9
+
10
+ def build_from_single_prompt(name, description: nil, inputs: {})
11
+ prompt = Prompt.find_by!(name: name)
12
+ validate_inputs!(prompt, inputs)
13
+
14
+ interpolated_message = prompt.interpolate(inputs)
15
+ message = build_ai_message(interpolated_message, description)
16
+ build_prompt_association(message, prompt, inputs)
17
+
18
+ message # Return the built message
19
+ end
20
+
21
+ def build_from_multiple_prompts(prompt_inputs, description: nil)
22
+ message = @conversation.ai_messages.build(
23
+ request: '',
24
+ change_description: description,
25
+ llm: @conversation.model_identifier,
26
+ tool: @conversation.tool
27
+ )
28
+
29
+ prompt_inputs.each do |prompt_name, inputs|
30
+ process_single_prompt_input(message, prompt_name, inputs)
31
+ end
32
+
33
+ message # Return the built message
34
+ end
35
+
36
+ def process_single_prompt_input(message, prompt_name, inputs)
37
+ prompt = Prompt.find_by!(name: prompt_name)
38
+ validate_inputs!(prompt, inputs)
39
+
40
+ interpolated_message = prompt.interpolate(inputs)
41
+ message.request += interpolated_message
42
+
43
+ message_prompt = message.ai_message_prompts.build(
44
+ prompt_version_id: prompt.latest_version_id,
45
+ draft: prompt.message
46
+ )
47
+ build_inputs_for_prompt(message_prompt, inputs)
48
+ end
49
+
50
+ private
51
+
52
+ def validate_inputs!(prompt, inputs)
53
+ return unless prompt.valid_placeholders.present?
54
+
55
+ missing_inputs, extra_inputs = calculate_input_discrepancies(prompt, inputs)
56
+
57
+ errors = []
58
+ errors << "Missing required inputs: #{missing_inputs.join(', ')}" if missing_inputs.any?
59
+ errors << "Unknown inputs provided: #{extra_inputs.join(', ')}" if extra_inputs.any?
60
+
61
+ raise ArgumentError, errors.join("\n") if errors.any?
62
+ end
63
+
64
+ def build_prompt_association(message, prompt, inputs)
65
+ message_prompt = message.ai_message_prompts.build(
66
+ prompt: prompt,
67
+ prompt_version_id: prompt.latest_version_id,
68
+ draft: prompt.message
69
+ )
70
+ build_inputs_for_prompt(message_prompt, inputs)
71
+ end
72
+
73
+ def build_inputs_for_prompt(message_prompt, inputs)
74
+ inputs.each do |input_name, value|
75
+ message_prompt.ai_message_inputs.build(
76
+ placeholder_name: input_name.to_s,
77
+ value: value.to_s
78
+ )
79
+ end
80
+ end
81
+
82
+ # Calculates missing and extra inputs compared to prompt placeholders
83
+ def calculate_input_discrepancies(prompt, inputs)
84
+ required_placeholders = prompt.valid_placeholders.split(',').map(&:strip)
85
+ provided_keys = inputs.keys.map(&:to_s)
86
+ missing_inputs = required_placeholders - provided_keys
87
+ extra_inputs = provided_keys - required_placeholders
88
+ [missing_inputs, extra_inputs]
89
+ end
90
+
91
+ def build_ai_message(interpolated_message, description)
92
+ @conversation.ai_messages.build(
93
+ request: interpolated_message,
94
+ change_description: description,
95
+ llm: @conversation.model_identifier,
96
+ tool: @conversation.tool
97
+ )
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # A prompt is a message template that can be used to generate messages for an AI conversation.
5
+ class Prompt < ActiveRecord::Base
6
+ include Versionable
7
+
8
+ # Constants
9
+ VALID_ROLES = %w[system user assistant].freeze
10
+
11
+ # Associations
12
+ has_many :versions, class_name: RubyConversations.configuration.prompt_version_class
13
+ has_many :message_prompts, class_name: RubyConversations.configuration.message_prompt_class
14
+ has_many :messages, through: :message_prompts, class_name: RubyConversations.configuration.message_class,
15
+ source: :ai_message, dependent: :nullify
16
+ belongs_to :organization, optional: true
17
+
18
+ # Validations
19
+ validates :message, presence: true
20
+ validates :name, presence: true, uniqueness: true
21
+ validates :temperature, numericality: { greater_than_or_equal_to: 0.0 }, allow_nil: true
22
+ validates :role, presence: true, inclusion: { in: VALID_ROLES }
23
+ validate :validate_placeholders_format
24
+
25
+ # Scopes
26
+ scope :active, -> { where(active: true) }
27
+ scope :by_role, ->(role) { where(role: role) }
28
+ scope :with_versions, -> { includes(:versions) }
29
+ scope :ordered_by_name, -> { order(:name) }
30
+ scope :internal, -> { where(internal: true) }
31
+ scope :external, -> { where(internal: false) }
32
+
33
+ # Class methods
34
+ def self.find_by_name!(name)
35
+ find_by!(name: name.to_s.strip)
36
+ end
37
+
38
+ def self.roles
39
+ VALID_ROLES
40
+ end
41
+
42
+ # Instance methods
43
+ def latest_version_id
44
+ populate_initial_version!
45
+ versions.last&.id
46
+ end
47
+
48
+ def populate_initial_version!
49
+ versions.create!(message:, temperature:, created_at:, updated_at:) if versions.empty?
50
+ end
51
+
52
+ def interpolate(inputs = {})
53
+ return message if placeholders.empty?
54
+
55
+ validate_required_variables!(inputs)
56
+
57
+ interpolated = message.dup
58
+ format(interpolated, **inputs)
59
+ end
60
+
61
+ def placeholders
62
+ return [] if message.nil?
63
+
64
+ matches = message.scan(/%<([^>]+)>/)
65
+ matches&.flatten&.uniq || []
66
+ end
67
+
68
+ def active?
69
+ active
70
+ end
71
+
72
+ def deactivate!
73
+ update!(active: false)
74
+ end
75
+
76
+ def activate!
77
+ update!(active: true)
78
+ end
79
+
80
+ private
81
+
82
+ def versioned_attributes
83
+ %i[content role]
84
+ end
85
+
86
+ def validate_placeholders_format
87
+ invalid_placeholders = placeholders.grep_v(/\A[a-z_][a-z0-9_]*\z/i)
88
+
89
+ return unless invalid_placeholders.any?
90
+
91
+ errors.add(:message, "contains invalid placeholders: #{invalid_placeholders.join(', ')}")
92
+ end
93
+
94
+ def validate_required_variables!(variables)
95
+ missing = placeholders - variables.keys.map(&:to_s)
96
+ raise ArgumentError, "Missing required variables: #{missing.join(', ')}" if missing.any?
97
+
98
+ extra = variables.keys.map(&:to_s) - placeholders
99
+ return unless extra.any?
100
+
101
+ raise ArgumentError, "Unknown variables provided: #{extra.join(', ')}"
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyConversations
4
+ # Represents a specific version of a Prompt template.
5
+ class PromptVersion < ActiveRecord::Base
6
+ # Associations
7
+ belongs_to :prompt
8
+
9
+ # Validations
10
+ validates :message, presence: true
11
+ validates :temperature, numericality: { greater_than_or_equal_to: 0.0 }, allow_nil: true
12
+
13
+ # Scopes
14
+ scope :ordered, -> { order(created_at: :desc) }
15
+ end
16
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/migration'
5
+
6
+ module RubyConversations
7
+ module Generators
8
+ # Generates the necessary files for installing the ruby_conversations engine.
9
+ class InstallGenerator < Rails::Generators::Base
10
+ include Rails::Generators::Migration
11
+
12
+ source_root File.expand_path('templates', __dir__)
13
+
14
+ def self.next_migration_number(path)
15
+ next_migration_number = current_migration_number(path) + 1
16
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
17
+ end
18
+
19
+ def copy_migrations
20
+ migration_template_details.each do |file, description|
21
+ migration_template(
22
+ "migrations/#{file}",
23
+ "db/migrate/#{file.sub('.erb', '')}",
24
+ migration_version: migration_version,
25
+ migration_description: description
26
+ )
27
+ end
28
+ end
29
+
30
+ def create_initializer
31
+ template 'initializer.rb.erb', 'config/initializers/ruby_conversations.rb'
32
+ end
33
+
34
+ def mount_engine
35
+ route "mount RubyConversations::Engine => '/ai', as: 'ruby_conversations'"
36
+ end
37
+
38
+ def display_post_install_message
39
+ readme 'README.md'
40
+ end
41
+
42
+ private
43
+
44
+ def migration_version
45
+ "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
46
+ end
47
+
48
+ def migration_template_details
49
+ {
50
+ 'create_prompts.rb.erb' => 'Create prompts table',
51
+ 'create_prompt_versions.rb.erb' => 'Create prompt versions table',
52
+ 'create_ai_conversations.rb.erb' => 'Create conversations table',
53
+ 'create_ai_messages.rb.erb' => 'Create messages table',
54
+ 'create_ai_message_prompts.rb.erb' => 'Create message prompts table',
55
+ 'create_ai_message_inputs.rb.erb' => 'Create message inputs table'
56
+ }
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ # RubyConversations Installation
2
+
3
+ The gem has been installed! Here are the next steps:
4
+
5
+ 1. Run the migrations:
6
+ ```bash
7
+ bin/rails db:migrate
8
+ ```
9
+
10
+ 2. Configure the gem in `config/initializers/ruby_conversations.rb`
11
+ - Set your storage mode (:local or :remote)
12
+ - If using remote mode, set your API URL and JWT secret
13
+ - Customize model names if needed
14
+
15
+ 3. For remote mode, ensure your API endpoint is accessible and JWT authentication is configured.
16
+
17
+ 4. Start using conversations in your models:
18
+ ```ruby
19
+ class MyModel < ApplicationRecord
20
+ has_one :conversation, as: :conversationable, class_name: 'AiConversation'
21
+ end
22
+
23
+ # Create a conversation
24
+ conversation = my_model.create_conversation!
25
+
26
+ # Ask a question using a prompt
27
+ conversation.ask("explain_code", inputs: { code: "def hello; end" })
28
+ ```
29
+
30
+ 5. The engine is mounted at `/ai`. You can change this in `config/routes.rb` if needed.
31
+
32
+ ## What's Next?
33
+
34
+ - Check out the documentation at https://github.com/yourusername/ruby_conversations
35
+ - Review the example app at https://github.com/yourusername/ruby_conversations_example
36
+ - Join our Discord community at https://discord.gg/ruby_conversations
37
+
38
+ ## Need Help?
39
+
40
+ - Open an issue on GitHub
41
+ - Check our FAQ at https://github.com/yourusername/ruby_conversations/wiki/FAQ
42
+ - Email support at support@example.com