dify_llm 1.9.2 → 1.14.1

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +27 -8
  3. data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
  4. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
  5. data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
  10. data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +2 -2
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
  23. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
  24. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
  25. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
  26. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
  27. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
  28. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
  29. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
  37. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
  38. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
  39. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
  40. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
  41. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
  42. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
  43. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
  44. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
  45. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
  46. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
  47. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
  48. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
  49. data/lib/generators/ruby_llm/generator_helpers.rb +37 -17
  50. data/lib/generators/ruby_llm/install/install_generator.rb +22 -18
  51. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +1 -1
  52. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +4 -1
  53. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +4 -10
  54. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +2 -1
  55. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -2
  56. data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
  57. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
  58. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
  59. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
  60. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
  61. data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
  62. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +19 -0
  63. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +50 -0
  64. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
  65. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
  66. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +2 -4
  67. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
  68. data/lib/ruby_llm/active_record/acts_as.rb +10 -4
  69. data/lib/ruby_llm/active_record/acts_as_legacy.rb +132 -27
  70. data/lib/ruby_llm/active_record/chat_methods.rb +132 -28
  71. data/lib/ruby_llm/active_record/message_methods.rb +58 -8
  72. data/lib/ruby_llm/active_record/model_methods.rb +1 -1
  73. data/lib/ruby_llm/active_record/payload_helpers.rb +26 -0
  74. data/lib/ruby_llm/active_record/tool_call_methods.rb +15 -0
  75. data/lib/ruby_llm/agent.rb +365 -0
  76. data/lib/ruby_llm/aliases.json +106 -61
  77. data/lib/ruby_llm/attachment.rb +8 -3
  78. data/lib/ruby_llm/chat.rb +150 -22
  79. data/lib/ruby_llm/configuration.rb +65 -65
  80. data/lib/ruby_llm/connection.rb +11 -7
  81. data/lib/ruby_llm/content.rb +6 -2
  82. data/lib/ruby_llm/error.rb +37 -1
  83. data/lib/ruby_llm/message.rb +43 -15
  84. data/lib/ruby_llm/model/info.rb +15 -13
  85. data/lib/ruby_llm/models.json +25039 -12260
  86. data/lib/ruby_llm/models.rb +185 -24
  87. data/lib/ruby_llm/provider.rb +26 -4
  88. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -119
  89. data/lib/ruby_llm/providers/anthropic/chat.rb +149 -17
  90. data/lib/ruby_llm/providers/anthropic/media.rb +2 -2
  91. data/lib/ruby_llm/providers/anthropic/models.rb +3 -9
  92. data/lib/ruby_llm/providers/anthropic/streaming.rb +25 -1
  93. data/lib/ruby_llm/providers/anthropic/tools.rb +20 -0
  94. data/lib/ruby_llm/providers/anthropic.rb +5 -1
  95. data/lib/ruby_llm/providers/azure/chat.rb +29 -0
  96. data/lib/ruby_llm/providers/azure/embeddings.rb +24 -0
  97. data/lib/ruby_llm/providers/azure/media.rb +45 -0
  98. data/lib/ruby_llm/providers/azure/models.rb +14 -0
  99. data/lib/ruby_llm/providers/azure.rb +148 -0
  100. data/lib/ruby_llm/providers/bedrock/auth.rb +122 -0
  101. data/lib/ruby_llm/providers/bedrock/chat.rb +357 -28
  102. data/lib/ruby_llm/providers/bedrock/media.rb +62 -33
  103. data/lib/ruby_llm/providers/bedrock/models.rb +104 -65
  104. data/lib/ruby_llm/providers/bedrock/streaming.rb +309 -8
  105. data/lib/ruby_llm/providers/bedrock.rb +69 -52
  106. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -114
  107. data/lib/ruby_llm/providers/deepseek.rb +5 -1
  108. data/lib/ruby_llm/providers/dify/chat.rb +82 -7
  109. data/lib/ruby_llm/providers/dify/media.rb +2 -2
  110. data/lib/ruby_llm/providers/dify/streaming.rb +26 -4
  111. data/lib/ruby_llm/providers/dify.rb +4 -0
  112. data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -207
  113. data/lib/ruby_llm/providers/gemini/chat.rb +88 -6
  114. data/lib/ruby_llm/providers/gemini/images.rb +1 -1
  115. data/lib/ruby_llm/providers/gemini/models.rb +2 -4
  116. data/lib/ruby_llm/providers/gemini/streaming.rb +34 -2
  117. data/lib/ruby_llm/providers/gemini/tools.rb +35 -3
  118. data/lib/ruby_llm/providers/gemini.rb +4 -0
  119. data/lib/ruby_llm/providers/gpustack/capabilities.rb +20 -0
  120. data/lib/ruby_llm/providers/gpustack/chat.rb +1 -1
  121. data/lib/ruby_llm/providers/gpustack.rb +8 -0
  122. data/lib/ruby_llm/providers/mistral/capabilities.rb +8 -0
  123. data/lib/ruby_llm/providers/mistral/chat.rb +59 -1
  124. data/lib/ruby_llm/providers/mistral.rb +4 -0
  125. data/lib/ruby_llm/providers/ollama/capabilities.rb +20 -0
  126. data/lib/ruby_llm/providers/ollama/chat.rb +1 -1
  127. data/lib/ruby_llm/providers/ollama.rb +11 -1
  128. data/lib/ruby_llm/providers/openai/capabilities.rb +96 -192
  129. data/lib/ruby_llm/providers/openai/chat.rb +101 -7
  130. data/lib/ruby_llm/providers/openai/media.rb +5 -2
  131. data/lib/ruby_llm/providers/openai/models.rb +2 -4
  132. data/lib/ruby_llm/providers/openai/streaming.rb +11 -3
  133. data/lib/ruby_llm/providers/openai/temperature.rb +28 -0
  134. data/lib/ruby_llm/providers/openai/tools.rb +27 -2
  135. data/lib/ruby_llm/providers/openai.rb +11 -1
  136. data/lib/ruby_llm/providers/openrouter/chat.rb +168 -0
  137. data/lib/ruby_llm/providers/openrouter/images.rb +69 -0
  138. data/lib/ruby_llm/providers/openrouter/streaming.rb +74 -0
  139. data/lib/ruby_llm/providers/openrouter.rb +37 -1
  140. data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
  141. data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
  142. data/lib/ruby_llm/providers/perplexity.rb +4 -0
  143. data/lib/ruby_llm/providers/vertexai/models.rb +1 -1
  144. data/lib/ruby_llm/providers/vertexai.rb +23 -7
  145. data/lib/ruby_llm/providers/xai/chat.rb +15 -0
  146. data/lib/ruby_llm/providers/xai/models.rb +75 -0
  147. data/lib/ruby_llm/providers/xai.rb +32 -0
  148. data/lib/ruby_llm/stream_accumulator.rb +120 -18
  149. data/lib/ruby_llm/streaming.rb +60 -57
  150. data/lib/ruby_llm/thinking.rb +49 -0
  151. data/lib/ruby_llm/tokens.rb +47 -0
  152. data/lib/ruby_llm/tool.rb +48 -3
  153. data/lib/ruby_llm/tool_call.rb +6 -3
  154. data/lib/ruby_llm/version.rb +1 -1
  155. data/lib/ruby_llm.rb +14 -8
  156. data/lib/tasks/models.rake +61 -22
  157. data/lib/tasks/release.rake +1 -1
  158. data/lib/tasks/ruby_llm.rake +9 -1
  159. data/lib/tasks/vcr.rake +33 -1
  160. metadata +67 -16
  161. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -13
  162. data/lib/ruby_llm/providers/bedrock/capabilities.rb +0 -167
  163. data/lib/ruby_llm/providers/bedrock/signing.rb +0 -831
  164. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -51
  165. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -71
  166. data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -67
  167. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -80
  168. data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -78
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b0ccd75734d4873be1de4cba718299f0c82442c0ecffda9d4e990b9c81823fe
4
- data.tar.gz: 5350541b348132f81e812daa4eed4c60e71e9c6203631dc64d6f711dbcea1aa5
3
+ metadata.gz: f41ce25e2b7426daa4062b1f695acc840431786659a3656c0783a02e4f269ae5
4
+ data.tar.gz: 60a3b97b763501fc0e8197cff198240ead2f161d8f56e1ef84da6142f0e490a5
5
5
  SHA512:
6
- metadata.gz: 01b39c3b4098d06b6dbb89bba11485a0951e608a6910157834e40440ce099a05f2004dd022f269fe5b11dce6bececd5e612836d01180eb3f8c642cef60eb0ec9
7
- data.tar.gz: eaa24c9d5c25adf9654d7108b290a80d5152dc1fd8e9f3d6da8672595ad4888e66eb41ecb47a3ceb2040bcf3f8fde13f1e30a93557a11d6d6e087ef238e649b3
6
+ metadata.gz: d1a25397ad9846b51ffcde0d518329eb4fc4e2d302e071085798e7d27f96828f78f439e402eda3f6327e1d23b15d780e868d79244465c59099c19c7b040a9cd1
7
+ data.tar.gz: 0b613b02fe1a6e90a2bd5df1d4595af5890a3ce54abbb55e8b0237803a3df31df3ad3028a6e670a82337e90b4f914106a5fdb1bf5c91eb8d745ea0158497d2cb
data/README.md CHANGED
@@ -7,12 +7,12 @@
7
7
 
8
8
  <strong>One *beautiful* Ruby API for GPT, Claude, Gemini, and more.</strong>
9
9
 
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*
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) — *Your AI coworker*
11
11
 
12
- [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg?a=10)](https://badge.fury.io/rb/ruby_llm)
12
+ [![Gem Version](https://badge.fury.io/rb/ruby_llm.svg)](https://badge.fury.io/rb/ruby_llm)
13
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
- [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg?a=2)](https://codecov.io/gh/crmne/ruby_llm)
15
+ [![codecov](https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg)](https://codecov.io/gh/crmne/ruby_llm)
16
16
 
17
17
  <a href="https://trendshift.io/repositories/13640" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13640" alt="crmne%2Fruby_llm | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
18
18
  </div>
@@ -22,7 +22,11 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
22
22
 
23
23
  ---
24
24
 
25
- Build chatbots, AI agents, RAG applications. Works with OpenAI, Anthropic, Google, AWS, local models, and any OpenAI-compatible API.
25
+ Build chatbots, AI agents, RAG applications. Works with OpenAI, xAI, Anthropic, Google, AWS, local models, and any OpenAI-compatible API.
26
+
27
+ ## From zero to AI chat app in under two minutes
28
+
29
+ https://github.com/user-attachments/assets/65422091-9338-47da-a303-92b918bd1345
26
30
 
27
31
  ## Why RubyLLM?
28
32
 
@@ -95,6 +99,17 @@ end
95
99
  chat.with_tool(Weather).ask "What's the weather in Berlin?"
96
100
  ```
97
101
 
102
+ ```ruby
103
+ # Define an agent with instructions + tools
104
+ class WeatherAssistant < RubyLLM::Agent
105
+ model "gpt-5-nano"
106
+ instructions "Be concise and always use tools for weather."
107
+ tools Weather
108
+ end
109
+
110
+ WeatherAssistant.new.ask "What's the weather in Berlin?"
111
+ ```
112
+
98
113
  ```ruby
99
114
  # Get structured output
100
115
  class ProductSchema < RubyLLM::Schema
@@ -118,12 +133,14 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
118
133
  * **Embeddings:** Generate embeddings with `RubyLLM.embed`
119
134
  * **Moderation:** Content safety with `RubyLLM.moderate`
120
135
  * **Tools:** Let AI call your Ruby methods
136
+ * **Agents:** Reusable assistants with `RubyLLM::Agent`
121
137
  * **Structured output:** JSON schemas that just work
122
138
  * **Streaming:** Real-time responses with blocks
123
139
  * **Rails:** ActiveRecord integration with `acts_as_chat`
124
140
  * **Async:** Fiber-based concurrency
125
- * **Model registry:** 500+ models with capability detection and pricing
126
- * **Providers:** OpenAI, Anthropic, Gemini, VertexAI, Bedrock, DeepSeek, Mistral, Ollama, OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API
141
+ * **Model registry:** 800+ models with capability detection and pricing
142
+ * **Extended thinking:** Control, view, and persist model deliberation
143
+ * **Providers:** OpenAI, xAI, Anthropic, Gemini, VertexAI, Bedrock, DeepSeek, Mistral, Ollama, OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API
127
144
 
128
145
  ## Installation
129
146
 
@@ -145,10 +162,12 @@ end
145
162
 
146
163
  ```bash
147
164
  # Install Rails Integration
148
- rails generate ruby_llm:install
165
+ bin/rails generate ruby_llm:install
166
+ bin/rails db:migrate
167
+ bin/rails ruby_llm:load_models # v1.13+
149
168
 
150
169
  # Add Chat UI (optional)
151
- rails generate ruby_llm:chat_ui
170
+ bin/rails generate ruby_llm:chat_ui
152
171
  ```
153
172
 
154
173
  ```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
@@ -0,0 +1,6 @@
1
+ class <%= agent_class_name %> < RubyLLM::Agent
2
+ # Change `Chat` to your app's chat model for Rails persistence.
3
+ # Remove this line to skip persistence and use plain RubyLLM chats.
4
+ chat_model Chat
5
+ instructions
6
+ end
@@ -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/_message.html.erb',
60
- "app/views/#{message_view_path}/_#{message_model_name.demodulize.underscore}.html.erb"
61
- template 'views/messages/_tool_calls.html.erb',
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
- 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"
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
- # 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
174
+ broadcasting_callbacks = <<-RUBY
151
175
 
152
- broadcast_append_chunk_method = <<-RUBY
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
- partial: "#{partial_path}/content",
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
- " #{broadcasting_code}\n#{broadcast_append_chunk_method}"
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 " #{broadcasting_code}", :yellow
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
- return unless prompt.present?
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
- @<%= chat_variable_name %> = <%= chat_model_name %>.create!(model: model)
17
- <%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, prompt)
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
- return unless content.present?
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
- <%= chat_job_class_name %>.perform_later(@<%= chat_variable_name %>.id, content)
8
-
9
- respond_to do |format|
10
- format.turbo_stream
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 %> = <%= model_model_name %>.all
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
@@ -3,10 +3,10 @@ class <%= chat_job_class_name %> < ApplicationJob
3
3
  <%= chat_variable_name %> = <%= chat_model_name %>.find(<%= chat_variable_name %>_id)
4
4
 
5
5
  <%= chat_variable_name %>.ask(content) do |chunk|
6
- if chunk.content && !chunk.content.blank?
6
+ if chunk.content && !chunk.content.empty?
7
7
  <%= message_variable_name %> = <%= chat_variable_name %>.<%= message_table_name %>.last
8
8
  <%= message_variable_name %>.broadcast_append_chunk(chunk.content)
9
9
  end
10
10
  end
11
11
  end
12
- end
12
+ 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>