ruby_llm 1.8.0 → 1.8.2

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 (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +15 -3
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +127 -67
  4. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +12 -12
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +7 -7
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  7. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +6 -6
  8. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +4 -4
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -5
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +5 -5
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +4 -4
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +8 -8
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +5 -5
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +9 -6
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +7 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -5
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +9 -9
  19. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +4 -6
  20. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +11 -11
  21. data/lib/generators/ruby_llm/generator_helpers.rb +131 -87
  22. data/lib/generators/ruby_llm/install/install_generator.rb +75 -79
  23. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +1 -1
  24. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +88 -85
  25. data/lib/ruby_llm/active_record/acts_as.rb +11 -2
  26. data/lib/ruby_llm/connection.rb +1 -1
  27. data/lib/ruby_llm/models.json +686 -840
  28. data/lib/ruby_llm/providers/openai/capabilities.rb +15 -7
  29. data/lib/ruby_llm/version.rb +1 -1
  30. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: def0767fe1f3e052f42bfddc4d3b69733d51cf8d39d057a08145307ef5f362a0
4
- data.tar.gz: fba9605e4458efb173dff1d131f0124c8252c4d71ca7a23527eb0d0a2543c75a
3
+ metadata.gz: 336eefa156c3f1b789ed32054beeb65b803dfa93c346274b4e812dc431bd6faf
4
+ data.tar.gz: 11f2c00c1eac1f24ad8beab2c4994fda980ac260e6481b261f3b0a515cc419a2
5
5
  SHA512:
6
- metadata.gz: 622b3b02ba5fb0cc0c49d7bc2d515e92abd0fa5714db70c8b0af64868cee9229b05ddefa6a990ab5f3788e7e07589628bd9f347f921346449a2154575e1e1ac1
7
- data.tar.gz: 890020ebc2817a3a388278510eaa2b31699eb660cce29ed1c19ebd5a67b3775f8ff83d9be58b31bac825719c6683d7ffcc321251a6f89dc58bafd135ea51ec25
6
+ metadata.gz: e4499bb85b6709d8fb2ff5e61d064f2352e265e03fa84b6f9aa1cba122c441f4239edc8e4ad5f9621fa4965411327d77a880b7496c145bcc2a3664ff4244545d
7
+ data.tar.gz: 440b540b7c514e72d1f4d916e04fe489cde96feb64f0699e1f7502b698f37d682b2e77b743efac212b259712553eb61703aae67e0a666fb0d05f02177382db38
data/README.md CHANGED
@@ -9,8 +9,8 @@
9
9
 
10
10
  Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com) — *Claude Code for your documents*
11
11
 
12
- [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=9)](https://badge.fury.io/rb/ruby_llm)
13
- [![Ruby Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://github.com/testdouble/standard)
12
+ [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=10)](https://badge.fury.io/rb/ruby_llm)
13
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
14
14
  [![Gem Downloads](https://img.shields.io/gem/dt/ruby_llm)](https://rubygems.org/gems/ruby_llm)
15
15
  [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg?a=2)](https://codecov.io/gh/crmne/ruby_llm)
16
16
 
@@ -69,6 +69,11 @@ RubyLLM.paint "a sunset over mountains in watercolor style"
69
69
  RubyLLM.embed "Ruby is elegant and expressive"
70
70
  ```
71
71
 
72
+ ```ruby
73
+ # Moderate content for safety
74
+ RubyLLM.moderate "Check if this text is safe"
75
+ ```
76
+
72
77
  ```ruby
73
78
  # Let AI use your code
74
79
  class Weather < RubyLLM::Tool
@@ -106,6 +111,7 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
106
111
  * **Documents:** Extract from PDFs, CSVs, JSON, any file type
107
112
  * **Image generation:** Create images with `RubyLLM.paint`
108
113
  * **Embeddings:** Vector search with `RubyLLM.embed`
114
+ * **Moderation:** Content safety with `RubyLLM.moderate`
109
115
  * **Tools:** Let AI call your Ruby methods
110
116
  * **Structured output:** JSON schemas that just work
111
117
  * **Streaming:** Real-time responses with blocks
@@ -133,7 +139,11 @@ end
133
139
  ## Rails
134
140
 
135
141
  ```bash
142
+ # Install Rails Integration
136
143
  rails generate ruby_llm:install
144
+
145
+ # Add Chat UI (optional)
146
+ rails generate ruby_llm:chat_ui
137
147
  ```
138
148
 
139
149
  ```ruby
@@ -141,10 +151,12 @@ class Chat < ApplicationRecord
141
151
  acts_as_chat
142
152
  end
143
153
 
144
- chat = Chat.create! model_id: "claude-sonnet-4"
154
+ chat = Chat.create! model: "claude-sonnet-4"
145
155
  chat.ask "What's in this file?", with: "report.pdf"
146
156
  ```
147
157
 
158
+ Visit `http://localhost:3000/chats` for a ready-to-use chat interface!
159
+
148
160
  ## Documentation
149
161
 
150
162
  [rubyllm.com](https://rubyllm.com)
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/generators'
4
+ require_relative '../generator_helpers'
4
5
 
5
6
  module RubyLLM
6
7
  module Generators
7
8
  # Generates a simple chat UI scaffold for RubyLLM
8
9
  class ChatUIGenerator < Rails::Generators::Base
10
+ include RubyLLM::Generators::GeneratorHelpers
11
+
9
12
  source_root File.expand_path('templates', __dir__)
10
13
 
11
14
  namespace 'ruby_llm:chat_ui'
@@ -15,113 +18,170 @@ module RubyLLM
15
18
  desc 'Creates a chat UI scaffold with Turbo streaming\n' \
16
19
  'Usage: rails g ruby_llm:chat_ui [chat:ChatName] [message:MessageName] ...'
17
20
 
18
- def parse_model_mappings
19
- @model_names = {
20
- chat: 'Chat',
21
- message: 'Message',
22
- tool_call: 'ToolCall',
23
- model: 'Model'
24
- }
25
-
26
- model_mappings.each do |mapping|
27
- if mapping.include?(':')
28
- key, value = mapping.split(':', 2)
29
- @model_names[key.to_sym] = value.classify
30
- end
31
- end
21
+ def check_model_exists
22
+ model_path = "app/models/#{message_model_name.underscore}.rb"
23
+ return if File.exist?(model_path)
32
24
 
33
- @model_names
34
- end
25
+ # Build the argument string for the install/upgrade commands
26
+ args = []
27
+ args << "chat:#{chat_model_name}" if chat_model_name != 'Chat'
28
+ args << "message:#{message_model_name}" if message_model_name != 'Message'
29
+ args << "model:#{model_model_name}" if model_model_name != 'Model'
30
+ args << "tool_call:#{tool_call_model_name}" if tool_call_model_name != 'ToolCall'
31
+ arg_string = args.any? ? " #{args.join(' ')}" : ''
35
32
 
36
- %i[chat message model].each do |type|
37
- define_method("#{type}_model_name") do
38
- @model_names ||= parse_model_mappings
39
- @model_names[type]
40
- end
33
+ raise Thor::Error, <<~ERROR
34
+ Model file not found: #{model_path}
41
35
 
42
- define_method("#{type}_table_name") do
43
- table_name_for(send("#{type}_model_name"))
44
- end
36
+ Please run the install generator first:
37
+ rails generate ruby_llm:install#{arg_string}
38
+
39
+ Or if upgrading from <= 1.6.x, run the upgrade generator:
40
+ rails generate ruby_llm:upgrade_to_v1_7#{arg_string}
41
+ ERROR
45
42
  end
46
43
 
47
44
  def create_views
45
+ # For namespaced models, use the proper Rails convention path
46
+ chat_view_path = chat_model_name.underscore.pluralize
47
+ message_view_path = message_model_name.underscore.pluralize
48
+ model_view_path = model_model_name.underscore.pluralize
49
+
48
50
  # Chat views
49
- template 'views/chats/index.html.erb', "app/views/#{chat_table_name}/index.html.erb"
50
- template 'views/chats/new.html.erb', "app/views/#{chat_table_name}/new.html.erb"
51
- template 'views/chats/show.html.erb', "app/views/#{chat_table_name}/show.html.erb"
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"
52
54
  template 'views/chats/_chat.html.erb',
53
- "app/views/#{chat_table_name}/_#{chat_model_name.underscore}.html.erb"
54
- template 'views/chats/_form.html.erb', "app/views/#{chat_table_name}/_form.html.erb"
55
+ "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"
55
57
 
56
58
  # Message views
57
59
  template 'views/messages/_message.html.erb',
58
- "app/views/#{message_table_name}/_#{message_model_name.underscore}.html.erb"
59
- template 'views/messages/_form.html.erb', "app/views/#{message_table_name}/_form.html.erb"
60
- template 'views/messages/create.turbo_stream.erb',
61
- "app/views/#{message_table_name}/create.turbo_stream.erb"
60
+ "app/views/#{message_view_path}/_#{message_model_name.demodulize.underscore}.html.erb"
61
+ template 'views/messages/_tool_calls.html.erb',
62
+ "app/views/#{message_view_path}/_tool_calls.html.erb"
63
+ template 'views/messages/_content.html.erb', "app/views/#{message_view_path}/_content.html.erb"
64
+ template 'views/messages/_form.html.erb', "app/views/#{message_view_path}/_form.html.erb"
65
+ template 'views/messages/create.turbo_stream.erb', "app/views/#{message_view_path}/create.turbo_stream.erb"
62
66
 
63
67
  # Model views
64
- template 'views/models/index.html.erb', "app/views/#{model_table_name}/index.html.erb"
65
- template 'views/models/show.html.erb', "app/views/#{model_table_name}/show.html.erb"
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"
66
70
  template 'views/models/_model.html.erb',
67
- "app/views/#{model_table_name}/_#{model_model_name.underscore}.html.erb"
71
+ "app/views/#{model_view_path}/_#{model_model_name.demodulize.underscore}.html.erb"
68
72
  end
69
73
 
70
74
  def create_controllers
71
- template 'controllers/chats_controller.rb', "app/controllers/#{chat_table_name}_controller.rb"
72
- template 'controllers/messages_controller.rb', "app/controllers/#{message_table_name}_controller.rb"
73
- template 'controllers/models_controller.rb', "app/controllers/#{model_table_name}_controller.rb"
75
+ # For namespaced models, use the proper Rails convention path
76
+ chat_controller_path = chat_model_name.underscore.pluralize
77
+ message_controller_path = message_model_name.underscore.pluralize
78
+ model_controller_path = model_model_name.underscore.pluralize
79
+
80
+ template 'controllers/chats_controller.rb', "app/controllers/#{chat_controller_path}_controller.rb"
81
+ template 'controllers/messages_controller.rb', "app/controllers/#{message_controller_path}_controller.rb"
82
+ template 'controllers/models_controller.rb', "app/controllers/#{model_controller_path}_controller.rb"
74
83
  end
75
84
 
76
85
  def create_jobs
77
- template 'jobs/chat_response_job.rb', "app/jobs/#{chat_model_name.underscore}_response_job.rb"
86
+ template 'jobs/chat_response_job.rb', "app/jobs/#{variable_name_for(chat_model_name)}_response_job.rb"
78
87
  end
79
88
 
80
89
  def add_routes
81
- model_routes = <<~ROUTES.strip
82
- resources :#{model_table_name}, only: [:index, :show] do
83
- collection do
84
- post :refresh
90
+ # For namespaced models, use Rails convention with namespace blocks
91
+ if chat_model_name.include?('::')
92
+ namespace = chat_model_name.deconstantize.underscore
93
+ chat_resource = chat_model_name.demodulize.underscore.pluralize
94
+ message_resource = message_model_name.demodulize.underscore.pluralize
95
+ model_resource = model_model_name.demodulize.underscore.pluralize
96
+
97
+ routes_content = <<~ROUTES.strip
98
+ namespace :#{namespace} do
99
+ resources :#{model_resource}, only: [:index, :show] do
100
+ collection do
101
+ post :refresh
102
+ end
103
+ end
104
+ resources :#{chat_resource} do
105
+ resources :#{message_resource}, only: [:create]
106
+ end
107
+ end
108
+ ROUTES
109
+ route routes_content
110
+ else
111
+ model_routes = <<~ROUTES.strip
112
+ resources :#{model_table_name}, only: [:index, :show] do
113
+ collection do
114
+ post :refresh
115
+ end
85
116
  end
86
- end
87
- ROUTES
88
- route model_routes
89
- chat_routes = <<~ROUTES.strip
90
- resources :#{chat_table_name} do
91
- resources :#{message_table_name}, only: [:create]
92
- end
93
- ROUTES
94
- route chat_routes
117
+ ROUTES
118
+ route model_routes
119
+ chat_routes = <<~ROUTES.strip
120
+ resources :#{chat_table_name} do
121
+ resources :#{message_table_name}, only: [:create]
122
+ end
123
+ ROUTES
124
+ route chat_routes
125
+ end
95
126
  end
96
127
 
97
128
  def add_broadcasting_to_message_model
98
- msg_var = message_model_name.underscore
99
- chat_var = chat_model_name.underscore
100
- broadcasting_code = "broadcasts_to ->(#{msg_var}) { \"#{chat_var}_\#{#{msg_var}.#{chat_var}_id}\" }"
129
+ msg_var = variable_name_for(message_model_name)
130
+ chat_var = variable_name_for(chat_model_name)
131
+ msg_path = message_model_name.underscore
132
+
133
+ # For namespaced models, we need the association name which might be different
134
+ # e.g., for LLM::Message, the chat association might be :llm_chat
135
+ chat_association = chat_table_name.singularize
136
+
137
+ # Use Rails convention paths for partials
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
151
+
152
+ broadcast_append_chunk_method = <<-RUBY
153
+
154
+ def broadcast_append_chunk(content)
155
+ broadcast_append_to "#{chat_var}_\#{#{chat_association}_id}",
156
+ target: "#{msg_var}_\#{id}_content",
157
+ partial: "#{partial_path}/content",
158
+ locals: { content: content }
159
+ end
160
+ RUBY
101
161
 
102
- inject_into_class "app/models/#{msg_var}.rb", message_model_name do
103
- "\n #{broadcasting_code}\n"
162
+ inject_into_file "app/models/#{msg_path}.rb", before: "end\n" do
163
+ " #{broadcasting_code}\n#{broadcast_append_chunk_method}"
104
164
  end
105
165
  rescue Errno::ENOENT
106
166
  say "#{message_model_name} model not found. Add broadcasting code to your model.", :yellow
107
167
  say " #{broadcasting_code}", :yellow
168
+ say broadcast_append_chunk_method, :yellow
108
169
  end
109
170
 
110
171
  def display_post_install_message
111
172
  return unless behavior == :invoke
112
173
 
174
+ # Show the correct URL based on whether models are namespaced
175
+ url_path = if chat_model_name.include?('::')
176
+ chat_model_name.underscore.pluralize
177
+ else
178
+ chat_table_name
179
+ end
180
+
113
181
  say "\n ✅ Chat UI installed!", :green
114
- say "\n Start your server and visit http://localhost:3000/#{chat_table_name}", :cyan
182
+ say "\n Start your server and visit http://localhost:3000/#{url_path}", :cyan
115
183
  say "\n"
116
184
  end
117
-
118
- private
119
-
120
- def table_name_for(model_name)
121
- # Convert namespaced model names to proper table names
122
- # e.g., "Assistant::Chat" -> "assistant_chats" (not "assistant/chats")
123
- model_name.underscore.pluralize.tr('/', '_')
124
- end
125
185
  end
126
186
  end
127
187
  end
@@ -1,39 +1,39 @@
1
- class <%= chat_model_name.pluralize %>Controller < ApplicationController
2
- before_action :set_<%= chat_model_name.underscore %>, only: [:show]
1
+ class <%= chat_controller_class_name %> < ApplicationController
2
+ before_action :set_<%= chat_variable_name %>, only: [:show]
3
3
 
4
4
  def index
5
- @<%= chat_model_name.tableize %> = <%= chat_model_name %>.order(created_at: :desc)
5
+ @<%= chat_table_name %> = <%= chat_model_name %>.order(created_at: :desc)
6
6
  end
7
7
 
8
8
  def new
9
- @<%= chat_model_name.underscore %> = <%= chat_model_name %>.new
9
+ @<%= chat_variable_name %> = <%= chat_model_name %>.new
10
10
  @selected_model = params[:model]
11
11
  end
12
12
 
13
13
  def create
14
14
  return unless prompt.present?
15
15
 
16
- @<%= chat_model_name.underscore %> = <%= chat_model_name %>.create!(model: model)
17
- <%= chat_model_name %>ResponseJob.perform_later(@<%= chat_model_name.underscore %>.id, prompt)
16
+ @<%= chat_variable_name %> = <%= chat_model_name %>.create!(model: model)
17
+ <%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, prompt)
18
18
 
19
- redirect_to @<%= chat_model_name.underscore %>, notice: '<%= chat_model_name.humanize %> was successfully created.'
19
+ redirect_to @<%= chat_variable_name %>, notice: '<%= chat_model_name.humanize %> was successfully created.'
20
20
  end
21
21
 
22
22
  def show
23
- @<%= message_model_name.underscore %> = @<%= chat_model_name.underscore %>.<%= message_model_name.tableize %>.build
23
+ @<%= message_variable_name %> = @<%= chat_variable_name %>.<%= message_table_name %>.build
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- def set_<%= chat_model_name.underscore %>
29
- @<%= chat_model_name.underscore %> = <%= chat_model_name %>.find(params[:id])
28
+ def set_<%= chat_variable_name %>
29
+ @<%= chat_variable_name %> = <%= chat_model_name %>.find(params[:id])
30
30
  end
31
31
 
32
32
  def model
33
- params[:<%= chat_model_name.underscore %>][:model].presence
33
+ params[:<%= chat_variable_name %>][:model].presence
34
34
  end
35
35
 
36
36
  def prompt
37
- params[:<%= chat_model_name.underscore %>][:prompt]
37
+ params[:<%= chat_variable_name %>][:prompt]
38
38
  end
39
39
  end
@@ -1,24 +1,24 @@
1
- class <%= message_model_name.pluralize %>Controller < ApplicationController
2
- before_action :set_<%= chat_model_name.underscore %>
1
+ class <%= message_controller_class_name %> < ApplicationController
2
+ before_action :set_<%= chat_variable_name %>
3
3
 
4
4
  def create
5
5
  return unless content.present?
6
6
 
7
- <%= chat_model_name %>ResponseJob.perform_later(@<%= chat_model_name.underscore %>.id, content)
7
+ <%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, content)
8
8
 
9
9
  respond_to do |format|
10
10
  format.turbo_stream
11
- format.html { redirect_to @<%= chat_model_name.underscore %> }
11
+ format.html { redirect_to @<%= chat_variable_name %> }
12
12
  end
13
13
  end
14
14
 
15
15
  private
16
16
 
17
- def set_<%= chat_model_name.underscore %>
18
- @<%= chat_model_name.underscore %> = <%= chat_model_name %>.find(params[:<%= chat_model_name.underscore %>_id])
17
+ def set_<%= chat_variable_name %>
18
+ @<%= chat_variable_name %> = <%= chat_model_name %>.find(params[:<%= chat_model_name.include?('::') ? chat_model_name.demodulize.underscore : chat_variable_name %>_id])
19
19
  end
20
20
 
21
21
  def content
22
- params[:<%= message_model_name.underscore %>][:content]
22
+ params[:<%= message_variable_name %>][:content]
23
23
  end
24
24
  end
@@ -1,14 +1,14 @@
1
- class <%= model_model_name.pluralize %>Controller < ApplicationController
1
+ class <%= model_controller_class_name %> < ApplicationController
2
2
  def index
3
- @<%= model_model_name.tableize %> = <%= model_model_name %>.all.group_by(&:provider)
3
+ @<%= model_table_name %> = <%= model_model_name %>.all
4
4
  end
5
5
 
6
6
  def show
7
- @<%= model_model_name.underscore %> = <%= model_model_name %>.find(params[:id])
7
+ @<%= model_variable_name %> = <%= model_model_name %>.find(params[:id])
8
8
  end
9
9
 
10
10
  def refresh
11
11
  <%= model_model_name %>.refresh!
12
- redirect_to <%= model_model_name.tableize %>_path, notice: "<%= model_model_name.pluralize %> refreshed successfully"
12
+ redirect_to <%= model_table_name %>_path, notice: "<%= model_model_name.pluralize %> refreshed successfully"
13
13
  end
14
14
  end
@@ -1,11 +1,11 @@
1
- class <%= chat_model_name %>ResponseJob < ApplicationJob
2
- def perform(<%= chat_model_name.underscore %>_id, content)
3
- <%= chat_model_name.underscore %> = <%= chat_model_name %>.find(<%= chat_model_name.underscore %>_id)
1
+ class <%= chat_job_class_name %> < ApplicationJob
2
+ def perform(<%= chat_variable_name %>_id, content)
3
+ <%= chat_variable_name %> = <%= chat_model_name %>.find(<%= chat_variable_name %>_id)
4
4
 
5
- <%= chat_model_name.underscore %>.ask(content) do |chunk|
5
+ <%= chat_variable_name %>.ask(content) do |chunk|
6
6
  if chunk.content && !chunk.content.blank?
7
- <%= message_model_name.underscore %> = <%= chat_model_name.underscore %>.<%= message_model_name.tableize %>.last
8
- <%= message_model_name.underscore %>.update!(content: <%= message_model_name.underscore %>.content + chunk.content)
7
+ <%= message_variable_name %> = <%= chat_variable_name %>.<%= message_table_name %>.last
8
+ <%= message_variable_name %>.broadcast_append_chunk(chunk.content)
9
9
  end
10
10
  end
11
11
  end
@@ -1,16 +1,16 @@
1
- <div id="<%%= dom_id <%= chat_model_name.underscore %> %>">
1
+ <div id="<%%= dom_id <%= chat_model_name.demodulize.underscore %> %>">
2
2
  <div>
3
3
  <strong>Model:</strong>
4
- <%%= <%= chat_model_name.underscore %>.model_association.name %>
4
+ <%%= <%= chat_model_name.demodulize.underscore %>.<%= model_table_name.singularize %>&.name || 'Default' %>
5
5
  </div>
6
6
 
7
7
  <div>
8
8
  <strong>Messages:</strong>
9
- <%%= <%= chat_model_name.underscore %>.messages_association.count %>
9
+ <%%= <%= chat_model_name.demodulize.underscore %>.<%= message_table_name %>.count %>
10
10
  </div>
11
11
 
12
12
  <div>
13
13
  <strong>Created:</strong>
14
- <%%= <%= chat_model_name.underscore %>.created_at.strftime("%B %d, %Y at %I:%M %p") %>
14
+ <%%= <%= chat_model_name.demodulize.underscore %>.created_at.strftime("%B %d, %Y at %I:%M %p") %>
15
15
  </div>
16
16
  </div>
@@ -1,10 +1,10 @@
1
- <%%= form_with(model: <%= chat_model_name.underscore %>, url: <%= chat_model_name.tableize %>_path) do |form| %>
2
- <%% if <%= chat_model_name.underscore %>.errors.any? %>
1
+ <%%= form_with(model: <%= chat_variable_name %>, url: <%= chat_table_name %>_path) do |form| %>
2
+ <%% if <%= chat_variable_name %>.errors.any? %>
3
3
  <div style="color: red">
4
- <h2><%%= pluralize(<%= chat_model_name.underscore %>.errors.count, "error") %> prohibited this <%= chat_model_name.underscore.humanize.downcase %> from being saved:</h2>
4
+ <h2><%%= pluralize(<%= chat_variable_name %>.errors.count, "error") %> prohibited this <%= chat_table_name.singularize.humanize.downcase %> from being saved:</h2>
5
5
 
6
6
  <ul>
7
- <%% <%= chat_model_name.underscore %>.errors.each do |error| %>
7
+ <%% <%= chat_variable_name %>.errors.each do |error| %>
8
8
  <li><%%= error.full_message %></li>
9
9
  <%% end %>
10
10
  </ul>
@@ -24,6 +24,6 @@
24
24
  </div>
25
25
 
26
26
  <div>
27
- <%%= form.submit "Start new <%= chat_model_name.underscore.humanize.downcase %>" %>
27
+ <%%= form.submit "Start new <%= chat_table_name.singularize.humanize.downcase %>" %>
28
28
  </div>
29
29
  <%% end %>
@@ -4,13 +4,13 @@
4
4
 
5
5
  <h1><%= chat_model_name.pluralize %></h1>
6
6
 
7
- <div id="<%= chat_model_name.tableize %>">
8
- <%% @<%= chat_model_name.tableize %>.each do |<%= chat_model_name.underscore %>| %>
9
- <%%= render <%= chat_model_name.underscore %> %>
7
+ <div id="<%= chat_table_name %>">
8
+ <%% @<%= chat_table_name %>.each do |<%= chat_variable_name %>| %>
9
+ <%%= render <%= chat_variable_name %> %>
10
10
  <p>
11
- <%%= link_to "Show this <%= chat_model_name.underscore.humanize.downcase %>", <%= chat_model_name.underscore %> %>
11
+ <%%= link_to "Show this <%= chat_table_name.singularize.humanize.downcase %>", <%= chat_variable_name %> %>
12
12
  </p>
13
13
  <%% end %>
14
14
  </div>
15
15
 
16
- <%%= link_to "New <%= chat_model_name.underscore.humanize.downcase %>", new_<%= chat_model_name.underscore %>_path %>
16
+ <%%= link_to "New <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path %>
@@ -1,11 +1,11 @@
1
- <%% content_for :title, "New <%= chat_model_name.underscore.humanize.downcase %>" %>
1
+ <%% content_for :title, "New <%= chat_table_name.singularize.humanize.downcase %>" %>
2
2
 
3
- <h1>New <%= chat_model_name.underscore.humanize.downcase %></h1>
3
+ <h1>New <%= chat_table_name.singularize.humanize.downcase %></h1>
4
4
 
5
- <%%= render "form", <%= chat_model_name.underscore %>: @<%= chat_model_name.underscore %> %>
5
+ <%%= render "form", <%= chat_variable_name %>: @<%= chat_variable_name %> %>
6
6
 
7
7
  <br>
8
8
 
9
9
  <div>
10
- <%%= link_to "Back to <%= chat_model_name.tableize.humanize.downcase %>", <%= chat_model_name.tableize %>_path %>
10
+ <%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
11
11
  </div>
@@ -1,23 +1,23 @@
1
1
  <p style="color: green"><%%= notice %></p>
2
2
 
3
- <%%= turbo_stream_from "<%= chat_model_name.underscore %>_#{@<%= chat_model_name.underscore %>.id}" %>
3
+ <%%= turbo_stream_from "<%= chat_variable_name %>_#{@<%= chat_variable_name %>.id}" %>
4
4
 
5
5
  <%% content_for :title, "<%= chat_model_name %>" %>
6
6
 
7
- <h1><%= chat_model_name %> <%%= @<%= chat_model_name.underscore %>.id %></h1>
7
+ <h1><%= chat_model_name %> <%%= @<%= chat_variable_name %>.id %></h1>
8
8
 
9
- <p>Using <strong><%%= @<%= chat_model_name.underscore %>.model_association.name %></strong></p>
9
+ <p>Using <strong><%%= @<%= chat_variable_name %>.<%= model_table_name.singularize %>.name %></strong></p>
10
10
 
11
- <div id="<%= message_model_name.tableize %>">
12
- <%% @<%= chat_model_name.underscore %>.messages_association.where.not(id: nil).each do |<%= message_model_name.underscore %>| %>
13
- <%%= render "<%= message_model_name.tableize %>/<%= message_model_name.underscore %>", <%= message_model_name.underscore %>: <%= message_model_name.underscore %> %>
11
+ <div id="<%= message_table_name %>">
12
+ <%% @<%= chat_variable_name %>.<%= message_table_name %>.where.not(id: nil).each do |<%= message_variable_name %>| %>
13
+ <%%= render <%= message_variable_name %> %>
14
14
  <%% end %>
15
15
  </div>
16
16
 
17
17
  <div style="margin-top: 30px;">
18
- <%%= render "<%= message_model_name.tableize %>/form", <%= chat_model_name.underscore %>: @<%= chat_model_name.underscore %>, <%= message_model_name.underscore %>: @<%= message_model_name.underscore %> %>
18
+ <%%= render "<%= message_model_name.underscore.pluralize %>/form", <%= chat_variable_name %>: @<%= chat_variable_name %>, <%= message_variable_name %>: @<%= message_variable_name %> %>
19
19
  </div>
20
20
 
21
21
  <div style="margin-top: 20px;">
22
- <%%= link_to "Back to <%= chat_model_name.tableize.humanize.downcase %>", <%= chat_model_name.tableize %>_path %>
22
+ <%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
23
23
  </div>
@@ -1,10 +1,10 @@
1
- <%%= form_with(model: [<%= chat_model_name.underscore %>, <%= message_model_name.underscore %>], id: "new_<%= message_model_name.underscore %>") do |form| %>
2
- <%% if <%= message_model_name.underscore %>.errors.any? %>
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 %>") do |form| %>
2
+ <%% if <%= message_variable_name %>.errors.any? %>
3
3
  <div style="color: red">
4
- <h2><%%= pluralize(<%= message_model_name.underscore %>.errors.count, "error") %> prohibited this <%= message_model_name.underscore.humanize.downcase %> from being saved:</h2>
4
+ <h2><%%= pluralize(<%= message_variable_name %>.errors.count, "error") %> prohibited this <%= message_table_name.singularize.humanize.downcase %> from being saved:</h2>
5
5
 
6
6
  <ul>
7
- <%% <%= message_model_name.underscore %>.errors.each do |error| %>
7
+ <%% <%= message_variable_name %>.errors.each do |error| %>
8
8
  <li><%%= error.full_message %></li>
9
9
  <%% end %>
10
10
  </ul>
@@ -16,6 +16,6 @@
16
16
  </div>
17
17
 
18
18
  <div>
19
- <%%= form.submit "Send <%= message_model_name.underscore.humanize.downcase %>" %>
19
+ <%%= form.submit "Send <%= message_table_name.singularize.humanize.downcase %>" %>
20
20
  </div>
21
21
  <%% end %>
@@ -1,10 +1,13 @@
1
- <%%= tag.div id: dom_id(<%= message_model_name.underscore %>), class: "<%= message_model_name.underscore %>",
2
- style: "margin-bottom: 20px; padding: 10px; border-left: 3px solid #{<%= message_model_name.underscore %>.role == 'user' ? '#007bff' : '#28a745'};" do %>
1
+ <div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>" class="<%= message_variable_name %>"
2
+ style="margin-bottom: 20px; padding: 10px; border-left: 3px solid <%%= <%= message_model_name.demodulize.underscore %>.role == 'user' ? '#007bff' : '#28a745' %>">
3
3
  <div style="font-weight: bold; margin-bottom: 5px;">
4
- <%%= <%= message_model_name.underscore %>.role&.capitalize %>
4
+ <%%= <%= message_model_name.demodulize.underscore %>.role&.capitalize %>
5
5
  </div>
6
- <div style="white-space: pre-wrap;"><%%= <%= message_model_name.underscore %>.content %></div>
6
+ <div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>_content" style="white-space: pre-wrap;"><%%= <%= message_model_name.demodulize.underscore %>.content %></div>
7
+ <%% if <%= message_model_name.demodulize.underscore %>.tool_call? %>
8
+ <%%= render "<%= message_model_name.underscore.pluralize %>/tool_calls", <%= message_model_name.demodulize.underscore %>: <%= message_model_name.demodulize.underscore %> %>
9
+ <%% end %>
7
10
  <div style="font-size: 0.85em; color: #666; margin-top: 5px;">
8
- <%%= <%= message_model_name.underscore %>.created_at&.strftime("%I:%M %p") %>
11
+ <%%= <%= message_model_name.demodulize.underscore %>.created_at&.strftime("%I:%M %p") %>
9
12
  </div>
10
- <%% end %>
13
+ </div>
@@ -0,0 +1,7 @@
1
+ <div style="display: flex; flex-direction: column; gap: 3px; align-items: flex-start; font-family: monospace;">
2
+ <%% <%= message_model_name.demodulize.underscore %>.<%= tool_call_variable_name.pluralize %>.each do |tool_call| %>
3
+ <div style="background: #eee; padding: 5px; border-radius: 4px;">
4
+ <%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)
5
+ </div>
6
+ <%% end %>
7
+ </div>