ruby_llm 1.13.2 → 1.14.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.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
- data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
- data/lib/generators/ruby_llm/generator_helpers.rb +33 -17
- data/lib/generators/ruby_llm/install/install_generator.rb +21 -18
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -4
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +1 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -2
- data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
- data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +1 -1
- data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +2 -4
- data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +2 -0
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +2 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +1 -1
- data/lib/ruby_llm/active_record/message_methods.rb +28 -0
- data/lib/ruby_llm/active_record/model_methods.rb +1 -1
- data/lib/ruby_llm/active_record/tool_call_methods.rb +28 -0
- data/lib/ruby_llm/agent.rb +11 -0
- data/lib/ruby_llm/aliases.json +15 -5
- data/lib/ruby_llm/attachment.rb +3 -0
- data/lib/ruby_llm/configuration.rb +54 -73
- data/lib/ruby_llm/connection.rb +1 -3
- data/lib/ruby_llm/error.rb +5 -0
- data/lib/ruby_llm/model/info.rb +14 -12
- data/lib/ruby_llm/models.json +2693 -2160
- data/lib/ruby_llm/models.rb +10 -3
- data/lib/ruby_llm/provider.rb +5 -0
- data/lib/ruby_llm/providers/anthropic.rb +4 -0
- data/lib/ruby_llm/providers/azure.rb +4 -0
- data/lib/ruby_llm/providers/bedrock.rb +4 -0
- data/lib/ruby_llm/providers/deepseek.rb +4 -0
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/gpustack.rb +4 -0
- data/lib/ruby_llm/providers/mistral.rb +4 -0
- data/lib/ruby_llm/providers/ollama.rb +4 -0
- data/lib/ruby_llm/providers/openai.rb +10 -0
- data/lib/ruby_llm/providers/openrouter/images.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +4 -0
- data/lib/ruby_llm/providers/perplexity.rb +4 -0
- data/lib/ruby_llm/providers/vertexai.rb +4 -0
- data/lib/ruby_llm/providers/xai.rb +4 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/release.rake +1 -1
- data/lib/tasks/ruby_llm.rake +6 -5
- data/lib/tasks/vcr.rake +1 -1
- metadata +47 -10
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -13
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ac485bae964a23af2c0a7ed48fa45fdb3207ad52d1ef6cbdca9b008b4c2429ba
|
|
4
|
+
data.tar.gz: 7219680107ccf2af1bd7378b0e1b50b2eb328280e8e896cf8f9af7ac270c761a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cfed1faf8354e9be7b39cfd44bd51596eaca75fb5d810bea7da72691ed7a7494a3ba779504acc20f8e92767be522bb3d6b77fab7db435ab91994b2db21255d2f
|
|
7
|
+
data.tar.gz: 39b8ed657d27655a1f179fd46915da6acf81a8d0144ef2891bacb4f156311ab1be924d07018b9a095005864f3fb750a63a793d93db6a44de22c4c9bf6fab040f
|
data/README.md
CHANGED
|
@@ -98,7 +98,7 @@ chat.with_tool(Weather).ask "What's the weather in Berlin?"
|
|
|
98
98
|
```ruby
|
|
99
99
|
# Define an agent with instructions + tools
|
|
100
100
|
class WeatherAssistant < RubyLLM::Agent
|
|
101
|
-
model "gpt-
|
|
101
|
+
model "gpt-5-nano"
|
|
102
102
|
instructions "Be concise and always use tools for weather."
|
|
103
103
|
tools Weather
|
|
104
104
|
end
|
|
@@ -158,12 +158,12 @@ end
|
|
|
158
158
|
|
|
159
159
|
```bash
|
|
160
160
|
# Install Rails Integration
|
|
161
|
-
rails generate ruby_llm:install
|
|
162
|
-
rails db:migrate
|
|
163
|
-
rails ruby_llm:load_models # v1.13+
|
|
161
|
+
bin/rails generate ruby_llm:install
|
|
162
|
+
bin/rails db:migrate
|
|
163
|
+
bin/rails ruby_llm:load_models # v1.13+
|
|
164
164
|
|
|
165
165
|
# Add Chat UI (optional)
|
|
166
|
-
rails generate ruby_llm:chat_ui
|
|
166
|
+
bin/rails generate ruby_llm:chat_ui
|
|
167
167
|
```
|
|
168
168
|
|
|
169
169
|
```ruby
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Generators
|
|
7
|
+
# Generator for RubyLLM agent classes and prompt files.
|
|
8
|
+
class AgentGenerator < Rails::Generators::NamedBase
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
namespace 'ruby_llm:agent'
|
|
12
|
+
|
|
13
|
+
desc 'Creates a RubyLLM agent class and default instructions prompt'
|
|
14
|
+
|
|
15
|
+
def create_agent_file
|
|
16
|
+
template 'agent.rb.tt', File.join('app/agents', class_path, "#{agent_file_name}.rb")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_prompt_file
|
|
20
|
+
empty_directory File.join('app/prompts', class_path, agent_file_name)
|
|
21
|
+
template 'instructions.txt.erb.tt',
|
|
22
|
+
File.join('app/prompts', class_path, agent_file_name, 'instructions.txt.erb')
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def agent_class_name
|
|
28
|
+
class_name.end_with?('Agent') ? class_name : "#{class_name}Agent"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def agent_file_name
|
|
32
|
+
agent_class_name.demodulize.underscore
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
File without changes
|
|
@@ -14,9 +14,11 @@ module RubyLLM
|
|
|
14
14
|
namespace 'ruby_llm:chat_ui'
|
|
15
15
|
|
|
16
16
|
argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
|
|
17
|
+
class_option :ui, type: :string, default: 'auto', enum: %w[scaffold tailwind auto],
|
|
18
|
+
desc: 'UI template style (scaffold, tailwind, auto)'
|
|
17
19
|
|
|
18
20
|
desc 'Creates a chat UI scaffold with Turbo streaming\n' \
|
|
19
|
-
'Usage: rails g ruby_llm:chat_ui [chat:ChatName] [message:MessageName] ...'
|
|
21
|
+
'Usage: bin/rails g ruby_llm:chat_ui [chat:ChatName] [message:MessageName] ...'
|
|
20
22
|
|
|
21
23
|
def check_model_exists
|
|
22
24
|
model_path = "app/models/#{message_model_name.underscore}.rb"
|
|
@@ -34,40 +36,54 @@ module RubyLLM
|
|
|
34
36
|
Model file not found: #{model_path}
|
|
35
37
|
|
|
36
38
|
Please run the install generator first:
|
|
37
|
-
rails generate ruby_llm:install#{arg_string}
|
|
39
|
+
bin/rails generate ruby_llm:install#{arg_string}
|
|
38
40
|
|
|
39
41
|
Or if upgrading from <= 1.6.x, run the upgrade generator:
|
|
40
|
-
rails generate ruby_llm:upgrade_to_v1_7#{arg_string}
|
|
42
|
+
bin/rails generate ruby_llm:upgrade_to_v1_7#{arg_string}
|
|
41
43
|
ERROR
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def create_views
|
|
47
|
+
# Design contract:
|
|
48
|
+
# - `scaffold` should stay close to Rails scaffold ERB output.
|
|
49
|
+
# - `tailwind` should stay close to tailwindcss-rails scaffold output.
|
|
50
|
+
# - Only small chat-specific affordances should be layered on top.
|
|
45
51
|
# For namespaced models, use the proper Rails convention path
|
|
46
52
|
chat_view_path = chat_model_name.underscore.pluralize
|
|
47
53
|
message_view_path = message_model_name.underscore.pluralize
|
|
48
54
|
model_view_path = model_model_name.underscore.pluralize
|
|
49
55
|
|
|
50
56
|
# Chat views
|
|
51
|
-
template 'views/chats/index.html.erb', "app/views/#{chat_view_path}/index.html.erb"
|
|
52
|
-
template 'views/chats/new.html.erb', "app/views/#{chat_view_path}/new.html.erb"
|
|
53
|
-
template 'views/chats/show.html.erb', "app/views/#{chat_view_path}/show.html.erb"
|
|
54
|
-
template 'views/chats/_chat.html.erb',
|
|
57
|
+
template ui_template('views/chats/index.html.erb'), "app/views/#{chat_view_path}/index.html.erb"
|
|
58
|
+
template ui_template('views/chats/new.html.erb'), "app/views/#{chat_view_path}/new.html.erb"
|
|
59
|
+
template ui_template('views/chats/show.html.erb'), "app/views/#{chat_view_path}/show.html.erb"
|
|
60
|
+
template ui_template('views/chats/_chat.html.erb'),
|
|
55
61
|
"app/views/#{chat_view_path}/_#{chat_model_name.demodulize.underscore}.html.erb"
|
|
56
|
-
template 'views/chats/_form.html.erb', "app/views/#{chat_view_path}/_form.html.erb"
|
|
62
|
+
template ui_template('views/chats/_form.html.erb'), "app/views/#{chat_view_path}/_form.html.erb"
|
|
57
63
|
|
|
58
64
|
# Message views
|
|
59
|
-
template 'views/messages/
|
|
60
|
-
|
|
61
|
-
template 'views/messages/
|
|
65
|
+
template ui_template('views/messages/_assistant.html.erb'), "app/views/#{message_view_path}/_assistant.html.erb"
|
|
66
|
+
template ui_template('views/messages/_user.html.erb'), "app/views/#{message_view_path}/_user.html.erb"
|
|
67
|
+
template ui_template('views/messages/_system.html.erb'), "app/views/#{message_view_path}/_system.html.erb"
|
|
68
|
+
template ui_template('views/messages/_tool.html.erb'), "app/views/#{message_view_path}/_tool.html.erb"
|
|
69
|
+
template ui_template('views/messages/_error.html.erb'), "app/views/#{message_view_path}/_error.html.erb"
|
|
70
|
+
template ui_template('views/messages/_tool_calls.html.erb'),
|
|
62
71
|
"app/views/#{message_view_path}/_tool_calls.html.erb"
|
|
63
|
-
|
|
64
|
-
template 'views/messages/
|
|
65
|
-
|
|
72
|
+
empty_directory "app/views/#{message_view_path}/tool_calls"
|
|
73
|
+
template ui_template('views/messages/tool_calls/_default.html.erb'),
|
|
74
|
+
"app/views/#{message_view_path}/tool_calls/_default.html.erb"
|
|
75
|
+
empty_directory "app/views/#{message_view_path}/tool_results"
|
|
76
|
+
template ui_template('views/messages/tool_results/_default.html.erb'),
|
|
77
|
+
"app/views/#{message_view_path}/tool_results/_default.html.erb"
|
|
78
|
+
template ui_template('views/messages/create.turbo_stream.erb'),
|
|
79
|
+
"app/views/#{message_view_path}/create.turbo_stream.erb"
|
|
80
|
+
template ui_template('views/messages/_content.html.erb'), "app/views/#{message_view_path}/_content.html.erb"
|
|
81
|
+
template ui_template('views/messages/_form.html.erb'), "app/views/#{message_view_path}/_form.html.erb"
|
|
66
82
|
|
|
67
83
|
# Model views
|
|
68
|
-
template 'views/models/index.html.erb', "app/views/#{model_view_path}/index.html.erb"
|
|
69
|
-
template 'views/models/show.html.erb', "app/views/#{model_view_path}/show.html.erb"
|
|
70
|
-
template 'views/models/_model.html.erb',
|
|
84
|
+
template ui_template('views/models/index.html.erb'), "app/views/#{model_view_path}/index.html.erb"
|
|
85
|
+
template ui_template('views/models/show.html.erb'), "app/views/#{model_view_path}/show.html.erb"
|
|
86
|
+
template ui_template('views/models/_model.html.erb'),
|
|
71
87
|
"app/views/#{model_view_path}/_#{model_model_name.demodulize.underscore}.html.erb"
|
|
72
88
|
end
|
|
73
89
|
|
|
@@ -86,6 +102,27 @@ module RubyLLM
|
|
|
86
102
|
template 'jobs/chat_response_job.rb', "app/jobs/#{variable_name_for(chat_model_name)}_response_job.rb"
|
|
87
103
|
end
|
|
88
104
|
|
|
105
|
+
def create_helpers
|
|
106
|
+
template 'helpers/messages_helper.rb', "app/helpers/#{message_model_name.underscore.pluralize}_helper.rb"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def add_available_chat_models_to_application_controller
|
|
110
|
+
path = 'app/controllers/application_controller.rb'
|
|
111
|
+
return unless File.exist?(path)
|
|
112
|
+
|
|
113
|
+
application_controller = File.read(path)
|
|
114
|
+
return if application_controller.include?('def available_chat_models')
|
|
115
|
+
|
|
116
|
+
inject_into_file path, <<-RUBY, before: /^end\s*\z/
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def available_chat_models
|
|
120
|
+
RubyLLM.models.chat_models.all
|
|
121
|
+
.sort_by { |model| [ model.provider.to_s, model.name.to_s ] }
|
|
122
|
+
end
|
|
123
|
+
RUBY
|
|
124
|
+
end
|
|
125
|
+
|
|
89
126
|
def add_routes
|
|
90
127
|
# For namespaced models, use Rails convention with namespace blocks
|
|
91
128
|
if chat_model_name.include?('::')
|
|
@@ -96,20 +133,20 @@ module RubyLLM
|
|
|
96
133
|
|
|
97
134
|
routes_content = <<~ROUTES.strip
|
|
98
135
|
namespace :#{namespace} do
|
|
99
|
-
resources :#{model_resource}, only: [:index, :show] do
|
|
136
|
+
resources :#{model_resource}, only: [ :index, :show ] do
|
|
100
137
|
collection do
|
|
101
138
|
post :refresh
|
|
102
139
|
end
|
|
103
140
|
end
|
|
104
141
|
resources :#{chat_resource} do
|
|
105
|
-
resources :#{message_resource}, only: [:create]
|
|
142
|
+
resources :#{message_resource}, only: [ :create ]
|
|
106
143
|
end
|
|
107
144
|
end
|
|
108
145
|
ROUTES
|
|
109
146
|
route routes_content
|
|
110
147
|
else
|
|
111
148
|
model_routes = <<~ROUTES.strip
|
|
112
|
-
resources :#{model_table_name}, only: [:index, :show] do
|
|
149
|
+
resources :#{model_table_name}, only: [ :index, :show ] do
|
|
113
150
|
collection do
|
|
114
151
|
post :refresh
|
|
115
152
|
end
|
|
@@ -118,7 +155,7 @@ module RubyLLM
|
|
|
118
155
|
route model_routes
|
|
119
156
|
chat_routes = <<~ROUTES.strip
|
|
120
157
|
resources :#{chat_table_name} do
|
|
121
|
-
resources :#{message_table_name}, only: [:create]
|
|
158
|
+
resources :#{message_table_name}, only: [ :create ]
|
|
122
159
|
end
|
|
123
160
|
ROUTES
|
|
124
161
|
route chat_routes
|
|
@@ -134,38 +171,23 @@ module RubyLLM
|
|
|
134
171
|
# e.g., for LLM::Message, the chat association might be :llm_chat
|
|
135
172
|
chat_association = chat_table_name.singularize
|
|
136
173
|
|
|
137
|
-
|
|
138
|
-
partial_path = message_model_name.underscore.pluralize
|
|
139
|
-
|
|
140
|
-
# For broadcasts, we need to explicitly set the partial path
|
|
141
|
-
# Turbo will pass the record with the demodulized name (e.g. 'message' for Llm::Message)
|
|
142
|
-
broadcasting_code = if message_model_name.include?('::')
|
|
143
|
-
partial_name = "#{partial_path}/#{message_model_name.demodulize.underscore}"
|
|
144
|
-
<<~RUBY.strip
|
|
145
|
-
broadcasts_to ->(#{msg_var}) { "#{chat_var}_\#{#{msg_var}.#{chat_association}_id}" },
|
|
146
|
-
partial: "#{partial_name}"
|
|
147
|
-
RUBY
|
|
148
|
-
else
|
|
149
|
-
"broadcasts_to ->(#{msg_var}) { \"#{chat_var}_\#{#{msg_var}.#{chat_association}_id}\" }"
|
|
150
|
-
end
|
|
174
|
+
broadcasting_callbacks = <<-RUBY
|
|
151
175
|
|
|
152
|
-
|
|
176
|
+
broadcasts_to ->(#{msg_var}) { "#{chat_var}_\#{#{msg_var}.#{chat_association}_id}" }, inserts_by: :append
|
|
153
177
|
|
|
154
178
|
def broadcast_append_chunk(content)
|
|
155
179
|
broadcast_append_to "#{chat_var}_\#{#{chat_association}_id}",
|
|
156
180
|
target: "#{msg_var}_\#{id}_content",
|
|
157
|
-
|
|
158
|
-
locals: { content: content }
|
|
181
|
+
content: ERB::Util.html_escape(content.to_s)
|
|
159
182
|
end
|
|
160
183
|
RUBY
|
|
161
184
|
|
|
162
185
|
inject_into_file "app/models/#{msg_path}.rb", before: "end\n" do
|
|
163
|
-
|
|
186
|
+
broadcasting_callbacks
|
|
164
187
|
end
|
|
165
188
|
rescue Errno::ENOENT
|
|
166
189
|
say "#{message_model_name} model not found. Add broadcasting code to your model.", :yellow
|
|
167
|
-
say
|
|
168
|
-
say broadcast_append_chunk_method, :yellow
|
|
190
|
+
say broadcasting_callbacks, :yellow
|
|
169
191
|
end
|
|
170
192
|
|
|
171
193
|
def display_post_install_message
|
|
@@ -179,9 +201,56 @@ module RubyLLM
|
|
|
179
201
|
end
|
|
180
202
|
|
|
181
203
|
say "\n ✅ Chat UI installed!", :green
|
|
204
|
+
say " UI template: #{ui_variant}", :cyan
|
|
182
205
|
say "\n Start your server and visit http://localhost:3000/#{url_path}", :cyan
|
|
183
206
|
say "\n"
|
|
184
207
|
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
|
|
211
|
+
def ui_variant
|
|
212
|
+
@ui_variant ||= case options[:ui]
|
|
213
|
+
when 'tailwind'
|
|
214
|
+
:tailwind
|
|
215
|
+
when 'auto'
|
|
216
|
+
tailwind_available? ? :tailwind : :scaffold
|
|
217
|
+
else
|
|
218
|
+
:scaffold
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def ui_template(template_path)
|
|
223
|
+
return template_path unless ui_variant == :tailwind
|
|
224
|
+
|
|
225
|
+
# Keep Tailwind templates as a separate set so we can mirror Rails/Tailwind
|
|
226
|
+
# scaffold conventions without complicating scaffold templates.
|
|
227
|
+
tailwind_template = "tailwind/#{template_path}"
|
|
228
|
+
File.exist?(File.join(self.class.source_root, "#{tailwind_template}.tt")) ? tailwind_template : template_path
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def message_helper_module_name
|
|
232
|
+
if message_model_name.include?('::')
|
|
233
|
+
"#{message_model_name.deconstantize}::#{message_model_name.demodulize.pluralize}Helper"
|
|
234
|
+
else
|
|
235
|
+
"#{message_model_name.pluralize}Helper"
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def tailwind_available?
|
|
240
|
+
Rails.root.join('app/assets/tailwind/application.css').exist? ||
|
|
241
|
+
Rails.root.join('config/tailwind.config.js').exist? ||
|
|
242
|
+
gem_in_bundle?('tailwindcss-rails') ||
|
|
243
|
+
gem_in_bundle?('cssbundling-rails')
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def gem_in_bundle?(gem_name)
|
|
247
|
+
gemfile_path = Rails.root.join('Gemfile')
|
|
248
|
+
lockfile_path = Rails.root.join('Gemfile.lock')
|
|
249
|
+
|
|
250
|
+
[gemfile_path, lockfile_path].any? do |path|
|
|
251
|
+
path.exist? && path.read.include?(gem_name)
|
|
252
|
+
end
|
|
253
|
+
end
|
|
185
254
|
end
|
|
186
255
|
end
|
|
187
256
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
class <%= chat_controller_class_name %> < ApplicationController
|
|
2
|
-
before_action :set_<%= chat_variable_name %>, only: [:show]
|
|
2
|
+
before_action :set_<%= chat_variable_name %>, only: [ :show, :destroy ]
|
|
3
3
|
|
|
4
4
|
def index
|
|
5
5
|
@<%= chat_table_name %> = <%= chat_model_name %>.order(created_at: :desc)
|
|
@@ -8,32 +8,31 @@ class <%= chat_controller_class_name %> < ApplicationController
|
|
|
8
8
|
def new
|
|
9
9
|
@<%= chat_variable_name %> = <%= chat_model_name %>.new
|
|
10
10
|
@selected_model = params[:model]
|
|
11
|
+
@chat_models = available_chat_models
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
def create
|
|
14
|
-
|
|
15
|
+
prompt = params.dig(:<%= chat_variable_name %>, :prompt)
|
|
16
|
+
if prompt.present?
|
|
17
|
+
@<%= chat_variable_name %> = <%= chat_model_name %>.create!(model: params.dig(:<%= chat_variable_name %>, :model).presence)
|
|
18
|
+
<%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, prompt)
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
redirect_to @<%= chat_variable_name %>, notice: '<%= chat_model_name.humanize %> was successfully created.'
|
|
20
|
+
redirect_to @<%= chat_variable_name %>, notice: "<%= chat_model_name.humanize %> was successfully created."
|
|
21
|
+
end
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
def show
|
|
23
25
|
@<%= message_variable_name %> = @<%= chat_variable_name %>.<%= message_table_name %>.build
|
|
24
26
|
end
|
|
25
27
|
|
|
28
|
+
def destroy
|
|
29
|
+
@<%= chat_variable_name %>.destroy!
|
|
30
|
+
redirect_to <%= chat_table_name %>_path, notice: "<%= chat_model_name.humanize %> was successfully destroyed.", status: :see_other
|
|
31
|
+
end
|
|
32
|
+
|
|
26
33
|
private
|
|
27
34
|
|
|
28
35
|
def set_<%= chat_variable_name %>
|
|
29
36
|
@<%= chat_variable_name %> = <%= chat_model_name %>.find(params[:id])
|
|
30
37
|
end
|
|
31
|
-
|
|
32
|
-
def model
|
|
33
|
-
params[:<%= chat_variable_name %>][:model].presence
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def prompt
|
|
37
|
-
params[:<%= chat_variable_name %>][:prompt]
|
|
38
|
-
end
|
|
39
|
-
end
|
|
38
|
+
end
|
|
@@ -2,13 +2,14 @@ class <%= message_controller_class_name %> < ApplicationController
|
|
|
2
2
|
before_action :set_<%= chat_variable_name %>
|
|
3
3
|
|
|
4
4
|
def create
|
|
5
|
-
|
|
5
|
+
content = params.dig(:<%= message_variable_name %>, :content)
|
|
6
|
+
if content.present?
|
|
7
|
+
<%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, content)
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
format.html { redirect_to @<%= chat_variable_name %> }
|
|
9
|
+
respond_to do |format|
|
|
10
|
+
format.turbo_stream
|
|
11
|
+
format.html { redirect_to @<%= chat_variable_name %> }
|
|
12
|
+
end
|
|
12
13
|
end
|
|
13
14
|
end
|
|
14
15
|
|
|
@@ -17,8 +18,4 @@ class <%= message_controller_class_name %> < ApplicationController
|
|
|
17
18
|
def set_<%= chat_variable_name %>
|
|
18
19
|
@<%= chat_variable_name %> = <%= chat_model_name %>.find(params[:<%= chat_model_name.include?('::') ? chat_model_name.demodulize.underscore : chat_variable_name %>_id])
|
|
19
20
|
end
|
|
20
|
-
|
|
21
|
-
def content
|
|
22
|
-
params[:<%= message_variable_name %>][:content]
|
|
23
|
-
end
|
|
24
|
-
end
|
|
21
|
+
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
class <%= model_controller_class_name %> < ApplicationController
|
|
2
2
|
def index
|
|
3
|
-
@<%= model_table_name %> =
|
|
3
|
+
@<%= model_table_name %> = available_chat_models
|
|
4
4
|
end
|
|
5
5
|
|
|
6
6
|
def show
|
|
@@ -11,4 +11,4 @@ class <%= model_controller_class_name %> < ApplicationController
|
|
|
11
11
|
<%= model_model_name %>.refresh!
|
|
12
12
|
redirect_to <%= model_table_name %>_path, notice: "<%= model_model_name.pluralize %> refreshed successfully"
|
|
13
13
|
end
|
|
14
|
-
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module <%= message_helper_module_name %>
|
|
2
|
+
def default_model_display_name
|
|
3
|
+
"Default: #{RubyLLM.models.find(RubyLLM.config.default_model).label}"
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def tool_result_partial(message)
|
|
7
|
+
name = message.respond_to?(:parent_tool_call) ? message.parent_tool_call&.name.to_s : ""
|
|
8
|
+
partial_for(prefix: "<%= message_model_name.underscore.pluralize %>/tool_results", name: name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def tool_call_partial(tool_call)
|
|
12
|
+
partial_for(prefix: "<%= message_model_name.underscore.pluralize %>/tool_calls", name: tool_call.name.to_s)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def partial_for(prefix:, name:)
|
|
18
|
+
normalized = name.to_s.underscore.tr("-", "_")
|
|
19
|
+
if normalized.present? && lookup_context.exists?(normalized, [ prefix ], true)
|
|
20
|
+
"#{prefix}/#{normalized}"
|
|
21
|
+
else
|
|
22
|
+
"#{prefix}/default"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div id="<%%= dom_id <%= chat_model_name.demodulize.underscore %> %>" class="w-full sm:w-auto my-5 space-y-3">
|
|
2
|
+
<div>
|
|
3
|
+
<strong class="block font-medium mb-1">Model:</strong>
|
|
4
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= model_table_name.singularize %>&.label || default_model_display_name %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div>
|
|
8
|
+
<strong class="block font-medium mb-1">Messages:</strong>
|
|
9
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= message_table_name %>.count %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<strong class="block font-medium mb-1">Created:</strong>
|
|
14
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.created_at.strftime("%B %d, %Y at %I:%M %p") %>
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%%= form_with(model: <%= chat_variable_name %>, url: <%= chat_table_name %>_path, class: "contents") do |form| %>
|
|
2
|
+
<%% if <%= chat_variable_name %>.errors.any? %>
|
|
3
|
+
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-md mt-3">
|
|
4
|
+
<h2><%%= pluralize(<%= chat_variable_name %>.errors.count, "error") %> prohibited this <%= chat_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul class="list-disc ml-6">
|
|
7
|
+
<%% <%= chat_variable_name %>.errors.each do |error| %>
|
|
8
|
+
<li><%%= error.full_message %></li>
|
|
9
|
+
<%% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<div class="my-5">
|
|
15
|
+
<%%= form.label :model, "Select AI model:" %>
|
|
16
|
+
<%%= form.select :model,
|
|
17
|
+
options_for_select(@chat_models.map { |model| [model.label, model.id] }.unshift([default_model_display_name, nil]), @selected_model),
|
|
18
|
+
{},
|
|
19
|
+
class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= chat_variable_name %>.errors[:model].none?, "border-red-400 focus:outline-red-600": <%= chat_variable_name %>.errors[:model].any?}] %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div class="my-5">
|
|
23
|
+
<%%= form.label :prompt, "Prompt" %>
|
|
24
|
+
<%%= form.text_area :prompt, rows: 4, placeholder: "What would you like to discuss?", autofocus: true,
|
|
25
|
+
class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= chat_variable_name %>.errors[:prompt].none?, "border-red-400 focus:outline-red-600": <%= chat_variable_name %>.errors[:prompt].any?}] %>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<div class="inline">
|
|
29
|
+
<%%= form.submit "Start new <%= chat_table_name.singularize.humanize.downcase %>", class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
|
|
30
|
+
</div>
|
|
31
|
+
<%% end %>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<%% content_for :title, "<%= chat_model_name.pluralize %>" %>
|
|
2
|
+
|
|
3
|
+
<div class="w-full">
|
|
4
|
+
<%% if notice.present? %>
|
|
5
|
+
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%%= notice %></p>
|
|
6
|
+
<%% end %>
|
|
7
|
+
|
|
8
|
+
<div class="flex justify-between items-center">
|
|
9
|
+
<h1 class="font-bold text-4xl"><%= chat_model_name.pluralize %></h1>
|
|
10
|
+
<div class="flex items-center gap-2">
|
|
11
|
+
<%%= link_to "<%= model_model_name.pluralize %>", <%= model_table_name %>_path, class: "rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 text-gray-900 block font-medium" %>
|
|
12
|
+
<%%= link_to "New <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path, class: "rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white block font-medium" %>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<div id="<%= chat_table_name %>" class="min-w-full divide-y divide-gray-200 space-y-5">
|
|
17
|
+
<%% if @<%= chat_table_name %>.any? %>
|
|
18
|
+
<%% @<%= chat_table_name %>.each do |<%= chat_variable_name %>| %>
|
|
19
|
+
<div class="flex flex-col sm:flex-row justify-between items-center pb-5 sm:pb-0">
|
|
20
|
+
<%%= render <%= chat_variable_name %> %>
|
|
21
|
+
<div class="w-full sm:w-auto flex flex-col sm:flex-row space-x-2 space-y-2">
|
|
22
|
+
<%%= link_to "Show", <%= chat_variable_name %>, class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
|
|
23
|
+
<%%= button_to "Destroy", <%= chat_variable_name %>, method: :delete, class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 bg-red-600 hover:bg-red-500 text-white inline-block font-medium cursor-pointer", data: { turbo_confirm: "Are you sure?" } %>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
<%% end %>
|
|
27
|
+
<%% else %>
|
|
28
|
+
<p class="text-center my-10">No <%= chat_table_name.humanize.downcase %> found.</p>
|
|
29
|
+
<%% end %>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%% content_for :title, "New <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
2
|
+
|
|
3
|
+
<div class="md:w-2/3 w-full">
|
|
4
|
+
<h1 class="font-bold text-4xl">New <%= chat_table_name.singularize.humanize.downcase %></h1>
|
|
5
|
+
|
|
6
|
+
<%%= render "form", <%= chat_variable_name %>: @<%= chat_variable_name %> %>
|
|
7
|
+
|
|
8
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
|
|
9
|
+
</div>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<%% content_for :title, "Showing <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
2
|
+
|
|
3
|
+
<%%= turbo_stream_from "<%= chat_variable_name %>_#{@<%= chat_variable_name %>.id}" %>
|
|
4
|
+
|
|
5
|
+
<%# Keep layout conventions aligned with tailwindcss-rails scaffold (top-left, md:w-2/3). -%>
|
|
6
|
+
<div class="md:w-2/3 w-full">
|
|
7
|
+
<%% if notice.present? %>
|
|
8
|
+
<p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%%= notice %></p>
|
|
9
|
+
<%% end %>
|
|
10
|
+
|
|
11
|
+
<h1 class="font-bold text-4xl">Showing <%= chat_table_name.singularize.humanize.downcase %> #<%%= @<%= chat_variable_name %>.id %></h1>
|
|
12
|
+
|
|
13
|
+
<div class="my-5">
|
|
14
|
+
<strong class="block font-medium mb-1">Model:</strong>
|
|
15
|
+
<%%= @<%= chat_variable_name %>.<%= model_table_name.singularize %>&.label || default_model_display_name %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div id="<%= message_table_name %>" class="min-w-full divide-y divide-gray-200 space-y-5 my-5">
|
|
19
|
+
<%% @<%= chat_variable_name %>.<%= message_table_name %>.where.not(id: nil).each do |<%= message_variable_name %>| %>
|
|
20
|
+
<%%= render <%= message_variable_name %> %>
|
|
21
|
+
<%% end %>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/form", <%= chat_variable_name %>: @<%= chat_variable_name %>, <%= message_variable_name %>: @<%= message_variable_name %> %>
|
|
25
|
+
|
|
26
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
|
|
27
|
+
</div>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<%% assistant ||= local_assigns[:message] %>
|
|
2
|
+
<div id="<%= message_variable_name %>_<%%= assistant.id %>" class="w-full sm:w-auto my-5 space-y-3 rounded-md px-3 py-2 bg-green-50">
|
|
3
|
+
<div>
|
|
4
|
+
<span class="inline-block rounded px-2 py-0.5 text-xs font-medium bg-green-100 text-green-700">
|
|
5
|
+
Assistant
|
|
6
|
+
</span>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div id="<%= message_variable_name %>_<%%= assistant.id %>_content" class="whitespace-pre-wrap"><%%= assistant.content %></div>
|
|
10
|
+
|
|
11
|
+
<div>
|
|
12
|
+
<span class="text-sm text-gray-600"><%%= assistant.created_at&.strftime("%I:%M %p") %></span>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%%= content -%>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>" class="w-full sm:w-auto my-5 space-y-3 rounded-md px-3 py-2 bg-red-50 border border-red-200">
|
|
2
|
+
<div>
|
|
3
|
+
<span class="inline-block rounded px-2 py-0.5 text-xs font-medium bg-red-100 text-red-700">
|
|
4
|
+
<%%= title.presence || "Error" %>
|
|
5
|
+
</span>
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<pre class="whitespace-pre-wrap text-red-900 text-sm overflow-x-auto"><%%= error_message %></pre>
|
|
9
|
+
|
|
10
|
+
<div>
|
|
11
|
+
<span class="text-sm text-red-700"><%%= <%= message_model_name.demodulize.underscore %>.created_at&.strftime("%I:%M %p") %></span>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<%%= form_with(model: <%= message_variable_name %>, url: <%= chat_model_name.include?('::') ? "#{chat_model_name.split('::').first.underscore}_#{chat_model_name.demodulize.underscore}_#{message_model_name.demodulize.underscore.pluralize}_path(@#{chat_variable_name})" : "[@#{chat_variable_name}, #{message_variable_name}]" %>, id: "new_<%= message_variable_name %>", class: "contents") do |form| %>
|
|
2
|
+
<%% if <%= message_variable_name %>.errors.any? %>
|
|
3
|
+
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-md mt-3">
|
|
4
|
+
<h2><%%= pluralize(<%= message_variable_name %>.errors.count, "error") %> prohibited this <%= message_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul class="list-disc ml-6">
|
|
7
|
+
<%% <%= message_variable_name %>.errors.each do |error| %>
|
|
8
|
+
<li><%%= error.full_message %></li>
|
|
9
|
+
<%% end %>
|
|
10
|
+
</ul>
|
|
11
|
+
</div>
|
|
12
|
+
<%% end %>
|
|
13
|
+
|
|
14
|
+
<div class="my-5">
|
|
15
|
+
<%%= form.label :content, "Message" %>
|
|
16
|
+
<%%= form.text_area :content, rows: 4, placeholder: "Message...", autofocus: true,
|
|
17
|
+
class: ["block shadow-sm rounded-md border px-3 py-2 mt-2 w-full", {"border-gray-400 focus:outline-blue-600": <%= message_variable_name %>.errors[:content].none?, "border-red-400 focus:outline-red-600": <%= message_variable_name %>.errors[:content].any?}] %>
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
<div class="inline">
|
|
21
|
+
<%%= form.submit "Send <%= message_table_name.singularize.humanize.downcase %>", class: "w-full sm:w-auto rounded-md px-3.5 py-2.5 bg-blue-600 hover:bg-blue-500 text-white inline-block font-medium cursor-pointer" %>
|
|
22
|
+
</div>
|
|
23
|
+
<%% end %>
|