ruby_llm_swarm 1.9.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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +175 -0
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +187 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +28 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/generator_helpers.rb +194 -0
- data/lib/generators/ruby_llm/install/install_generator.rb +106 -0
- data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +9 -0
- data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +7 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +16 -0
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +45 -0
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +20 -0
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +12 -0
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -0
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +3 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +145 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +124 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
- data/lib/ruby_llm/active_record/acts_as.rb +174 -0
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +384 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +350 -0
- data/lib/ruby_llm/active_record/message_methods.rb +81 -0
- data/lib/ruby_llm/active_record/model_methods.rb +84 -0
- data/lib/ruby_llm/aliases.json +295 -0
- data/lib/ruby_llm/aliases.rb +38 -0
- data/lib/ruby_llm/attachment.rb +220 -0
- data/lib/ruby_llm/chat.rb +816 -0
- data/lib/ruby_llm/chunk.rb +6 -0
- data/lib/ruby_llm/configuration.rb +78 -0
- data/lib/ruby_llm/connection.rb +126 -0
- data/lib/ruby_llm/content.rb +73 -0
- data/lib/ruby_llm/context.rb +29 -0
- data/lib/ruby_llm/embedding.rb +29 -0
- data/lib/ruby_llm/error.rb +84 -0
- data/lib/ruby_llm/image.rb +49 -0
- data/lib/ruby_llm/message.rb +86 -0
- data/lib/ruby_llm/mime_type.rb +71 -0
- data/lib/ruby_llm/model/info.rb +111 -0
- data/lib/ruby_llm/model/modalities.rb +22 -0
- data/lib/ruby_llm/model/pricing.rb +48 -0
- data/lib/ruby_llm/model/pricing_category.rb +46 -0
- data/lib/ruby_llm/model/pricing_tier.rb +33 -0
- data/lib/ruby_llm/model.rb +7 -0
- data/lib/ruby_llm/models.json +33198 -0
- data/lib/ruby_llm/models.rb +231 -0
- data/lib/ruby_llm/models_schema.json +168 -0
- data/lib/ruby_llm/moderation.rb +56 -0
- data/lib/ruby_llm/provider.rb +243 -0
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +134 -0
- data/lib/ruby_llm/providers/anthropic/chat.rb +125 -0
- data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +20 -0
- data/lib/ruby_llm/providers/anthropic/media.rb +92 -0
- data/lib/ruby_llm/providers/anthropic/models.rb +63 -0
- data/lib/ruby_llm/providers/anthropic/streaming.rb +45 -0
- data/lib/ruby_llm/providers/anthropic/tools.rb +109 -0
- data/lib/ruby_llm/providers/anthropic.rb +36 -0
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +167 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/media.rb +61 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +98 -0
- data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +51 -0
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +71 -0
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +67 -0
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +80 -0
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +78 -0
- data/lib/ruby_llm/providers/bedrock/streaming.rb +18 -0
- data/lib/ruby_llm/providers/bedrock.rb +82 -0
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +130 -0
- data/lib/ruby_llm/providers/deepseek/chat.rb +16 -0
- data/lib/ruby_llm/providers/deepseek.rb +30 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +281 -0
- data/lib/ruby_llm/providers/gemini/chat.rb +454 -0
- data/lib/ruby_llm/providers/gemini/embeddings.rb +37 -0
- data/lib/ruby_llm/providers/gemini/images.rb +47 -0
- data/lib/ruby_llm/providers/gemini/media.rb +112 -0
- data/lib/ruby_llm/providers/gemini/models.rb +40 -0
- data/lib/ruby_llm/providers/gemini/streaming.rb +61 -0
- data/lib/ruby_llm/providers/gemini/tools.rb +198 -0
- data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
- data/lib/ruby_llm/providers/gemini.rb +37 -0
- data/lib/ruby_llm/providers/gpustack/chat.rb +27 -0
- data/lib/ruby_llm/providers/gpustack/media.rb +46 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +90 -0
- data/lib/ruby_llm/providers/gpustack.rb +34 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +155 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +24 -0
- data/lib/ruby_llm/providers/mistral/embeddings.rb +33 -0
- data/lib/ruby_llm/providers/mistral/models.rb +48 -0
- data/lib/ruby_llm/providers/mistral.rb +32 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +27 -0
- data/lib/ruby_llm/providers/ollama/media.rb +46 -0
- data/lib/ruby_llm/providers/ollama/models.rb +36 -0
- data/lib/ruby_llm/providers/ollama.rb +30 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +299 -0
- data/lib/ruby_llm/providers/openai/chat.rb +88 -0
- data/lib/ruby_llm/providers/openai/embeddings.rb +33 -0
- data/lib/ruby_llm/providers/openai/images.rb +38 -0
- data/lib/ruby_llm/providers/openai/media.rb +81 -0
- data/lib/ruby_llm/providers/openai/models.rb +39 -0
- data/lib/ruby_llm/providers/openai/moderation.rb +34 -0
- data/lib/ruby_llm/providers/openai/streaming.rb +46 -0
- data/lib/ruby_llm/providers/openai/tools.rb +98 -0
- data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
- data/lib/ruby_llm/providers/openai.rb +44 -0
- data/lib/ruby_llm/providers/openai_responses.rb +395 -0
- data/lib/ruby_llm/providers/openrouter/models.rb +73 -0
- data/lib/ruby_llm/providers/openrouter.rb +26 -0
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +137 -0
- data/lib/ruby_llm/providers/perplexity/chat.rb +16 -0
- data/lib/ruby_llm/providers/perplexity/models.rb +42 -0
- data/lib/ruby_llm/providers/perplexity.rb +48 -0
- data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
- data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
- data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
- data/lib/ruby_llm/providers/vertexai.rb +55 -0
- data/lib/ruby_llm/railtie.rb +35 -0
- data/lib/ruby_llm/responses_session.rb +77 -0
- data/lib/ruby_llm/stream_accumulator.rb +101 -0
- data/lib/ruby_llm/streaming.rb +153 -0
- data/lib/ruby_llm/tool.rb +209 -0
- data/lib/ruby_llm/tool_call.rb +22 -0
- data/lib/ruby_llm/tool_executors.rb +125 -0
- data/lib/ruby_llm/transcription.rb +35 -0
- data/lib/ruby_llm/utils.rb +91 -0
- data/lib/ruby_llm/version.rb +5 -0
- data/lib/ruby_llm.rb +140 -0
- data/lib/tasks/models.rake +525 -0
- data/lib/tasks/release.rake +67 -0
- data/lib/tasks/ruby_llm.rake +15 -0
- data/lib/tasks/vcr.rake +92 -0
- metadata +346 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2b929914f2235d8170762331427f953b7e43d718d8372fdbe73e559ad1d5fbdf
|
|
4
|
+
data.tar.gz: 87112298cccfc79b10af119e18d5bbfa0171a165bae5ab7e218bab376b526205
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: da232b16c619e4a4eea8b168201a82769abd8c7871f3a0cc4b1b729618e6b830828f592a750cf1bfb0be9cfee72477c51a6298d815d1ce3c5cea78fb9612c644
|
|
7
|
+
data.tar.gz: 378c4ed4fbc8a9969fb35af754672bab0d77ffd4340040ca3b08ca1385a457045ee70858b57fa74e4aca346ed79f26bbf4207e9ea444e2e33a06f530ed15125b
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Carmine Paolino
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="/docs/assets/images/logotype_dark.svg">
|
|
5
|
+
<img src="/docs/assets/images/logotype.svg" alt="RubyLLM" height="120" width="250">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
<strong>One *beautiful* Ruby API for GPT, Claude, Gemini, and more.</strong>
|
|
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*
|
|
11
|
+
|
|
12
|
+
[](https://badge.fury.io/rb/ruby_llm)
|
|
13
|
+
[](https://github.com/rubocop/rubocop)
|
|
14
|
+
[](https://rubygems.org/gems/ruby_llm)
|
|
15
|
+
[](https://codecov.io/gh/crmne/ruby_llm)
|
|
16
|
+
|
|
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
|
+
</div>
|
|
19
|
+
|
|
20
|
+
> [!NOTE]
|
|
21
|
+
> Using RubyLLM? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
Build chatbots, AI agents, RAG applications. Works with OpenAI, Anthropic, Google, AWS, local models, and any OpenAI-compatible API.
|
|
26
|
+
|
|
27
|
+
## Why RubyLLM?
|
|
28
|
+
|
|
29
|
+
Every AI provider ships their own bloated client. Different APIs. Different response formats. Different conventions. It's exhausting.
|
|
30
|
+
|
|
31
|
+
RubyLLM gives you one beautiful API for all of them. Same interface whether you're using GPT, Claude, or your local Ollama. Just three dependencies: Faraday, Zeitwerk, and Marcel. That's it.
|
|
32
|
+
|
|
33
|
+
## Show me the code
|
|
34
|
+
|
|
35
|
+
```ruby
|
|
36
|
+
# Just ask questions
|
|
37
|
+
chat = RubyLLM.chat
|
|
38
|
+
chat.ask "What's the best way to learn Ruby?"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
# Analyze any file type
|
|
43
|
+
chat.ask "What's in this image?", with: "ruby_conf.jpg"
|
|
44
|
+
chat.ask "What's happening in this video?", with: "video.mp4"
|
|
45
|
+
chat.ask "Describe this meeting", with: "meeting.wav"
|
|
46
|
+
chat.ask "Summarize this document", with: "contract.pdf"
|
|
47
|
+
chat.ask "Explain this code", with: "app.rb"
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Multiple files at once
|
|
52
|
+
chat.ask "Analyze these files", with: ["diagram.png", "report.pdf", "notes.txt"]
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
# Stream responses
|
|
57
|
+
chat.ask "Tell me a story about Ruby" do |chunk|
|
|
58
|
+
print chunk.content
|
|
59
|
+
end
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
# Generate images
|
|
64
|
+
RubyLLM.paint "a sunset over mountains in watercolor style"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
# Create embeddings
|
|
69
|
+
RubyLLM.embed "Ruby is elegant and expressive"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
# Transcribe audio to text
|
|
74
|
+
RubyLLM.transcribe "meeting.wav"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
# Moderate content for safety
|
|
79
|
+
RubyLLM.moderate "Check if this text is safe"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
# Let AI use your code
|
|
84
|
+
class Weather < RubyLLM::Tool
|
|
85
|
+
description "Get current weather"
|
|
86
|
+
param :latitude
|
|
87
|
+
param :longitude
|
|
88
|
+
|
|
89
|
+
def execute(latitude:, longitude:)
|
|
90
|
+
url = "https://api.open-meteo.com/v1/forecast?latitude=#{latitude}&longitude=#{longitude}¤t=temperature_2m,wind_speed_10m"
|
|
91
|
+
JSON.parse(Faraday.get(url).body)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
chat.with_tool(Weather).ask "What's the weather in Berlin?"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```ruby
|
|
99
|
+
# Get structured output
|
|
100
|
+
class ProductSchema < RubyLLM::Schema
|
|
101
|
+
string :name
|
|
102
|
+
number :price
|
|
103
|
+
array :features do
|
|
104
|
+
string
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "product.txt"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Features
|
|
112
|
+
|
|
113
|
+
* **Chat:** Conversational AI with `RubyLLM.chat`
|
|
114
|
+
* **Vision:** Analyze images and videos
|
|
115
|
+
* **Audio:** Transcribe and understand speech with `RubyLLM.transcribe`
|
|
116
|
+
* **Documents:** Extract from PDFs, CSVs, JSON, any file type
|
|
117
|
+
* **Image generation:** Create images with `RubyLLM.paint`
|
|
118
|
+
* **Embeddings:** Generate embeddings with `RubyLLM.embed`
|
|
119
|
+
* **Moderation:** Content safety with `RubyLLM.moderate`
|
|
120
|
+
* **Tools:** Let AI call your Ruby methods
|
|
121
|
+
* **Structured output:** JSON schemas that just work
|
|
122
|
+
* **Streaming:** Real-time responses with blocks
|
|
123
|
+
* **Rails:** ActiveRecord integration with `acts_as_chat`
|
|
124
|
+
* **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
|
|
127
|
+
|
|
128
|
+
## Installation
|
|
129
|
+
|
|
130
|
+
Add to your Gemfile:
|
|
131
|
+
```ruby
|
|
132
|
+
gem 'ruby_llm'
|
|
133
|
+
```
|
|
134
|
+
Then `bundle install`.
|
|
135
|
+
|
|
136
|
+
Configure your API keys:
|
|
137
|
+
```ruby
|
|
138
|
+
# config/initializers/ruby_llm.rb
|
|
139
|
+
RubyLLM.configure do |config|
|
|
140
|
+
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
141
|
+
end
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Rails
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Install Rails Integration
|
|
148
|
+
rails generate ruby_llm:install
|
|
149
|
+
|
|
150
|
+
# Add Chat UI (optional)
|
|
151
|
+
rails generate ruby_llm:chat_ui
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
class Chat < ApplicationRecord
|
|
156
|
+
acts_as_chat
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
chat = Chat.create! model: "claude-sonnet-4"
|
|
160
|
+
chat.ask "What's in this file?", with: "report.pdf"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Visit `http://localhost:3000/chats` for a ready-to-use chat interface!
|
|
164
|
+
|
|
165
|
+
## Documentation
|
|
166
|
+
|
|
167
|
+
[rubyllm.com](https://rubyllm.com)
|
|
168
|
+
|
|
169
|
+
## Contributing
|
|
170
|
+
|
|
171
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
Released under the MIT License.
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require_relative '../generator_helpers'
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module Generators
|
|
8
|
+
# Generates a simple chat UI scaffold for RubyLLM
|
|
9
|
+
class ChatUIGenerator < Rails::Generators::Base
|
|
10
|
+
include RubyLLM::Generators::GeneratorHelpers
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path('templates', __dir__)
|
|
13
|
+
|
|
14
|
+
namespace 'ruby_llm:chat_ui'
|
|
15
|
+
|
|
16
|
+
argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
|
|
17
|
+
|
|
18
|
+
desc 'Creates a chat UI scaffold with Turbo streaming\n' \
|
|
19
|
+
'Usage: rails g ruby_llm:chat_ui [chat:ChatName] [message:MessageName] ...'
|
|
20
|
+
|
|
21
|
+
def check_model_exists
|
|
22
|
+
model_path = "app/models/#{message_model_name.underscore}.rb"
|
|
23
|
+
return if File.exist?(model_path)
|
|
24
|
+
|
|
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(' ')}" : ''
|
|
32
|
+
|
|
33
|
+
raise Thor::Error, <<~ERROR
|
|
34
|
+
Model file not found: #{model_path}
|
|
35
|
+
|
|
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
|
|
42
|
+
end
|
|
43
|
+
|
|
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
|
+
|
|
50
|
+
# 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',
|
|
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"
|
|
57
|
+
|
|
58
|
+
# 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',
|
|
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"
|
|
66
|
+
|
|
67
|
+
# 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',
|
|
71
|
+
"app/views/#{model_view_path}/_#{model_model_name.demodulize.underscore}.html.erb"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def create_controllers
|
|
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"
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def create_jobs
|
|
86
|
+
template 'jobs/chat_response_job.rb', "app/jobs/#{variable_name_for(chat_model_name)}_response_job.rb"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def add_routes
|
|
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
|
|
116
|
+
end
|
|
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
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def add_broadcasting_to_message_model
|
|
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
|
|
161
|
+
|
|
162
|
+
inject_into_file "app/models/#{msg_path}.rb", before: "end\n" do
|
|
163
|
+
" #{broadcasting_code}\n#{broadcast_append_chunk_method}"
|
|
164
|
+
end
|
|
165
|
+
rescue Errno::ENOENT
|
|
166
|
+
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
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def display_post_install_message
|
|
172
|
+
return unless behavior == :invoke
|
|
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
|
+
|
|
181
|
+
say "\n ✅ Chat UI installed!", :green
|
|
182
|
+
say "\n Start your server and visit http://localhost:3000/#{url_path}", :cyan
|
|
183
|
+
say "\n"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
class <%= chat_controller_class_name %> < ApplicationController
|
|
2
|
+
before_action :set_<%= chat_variable_name %>, only: [:show]
|
|
3
|
+
|
|
4
|
+
def index
|
|
5
|
+
@<%= chat_table_name %> = <%= chat_model_name %>.order(created_at: :desc)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def new
|
|
9
|
+
@<%= chat_variable_name %> = <%= chat_model_name %>.new
|
|
10
|
+
@selected_model = params[:model]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create
|
|
14
|
+
return unless prompt.present?
|
|
15
|
+
|
|
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
|
+
end
|
|
21
|
+
|
|
22
|
+
def show
|
|
23
|
+
@<%= message_variable_name %> = @<%= chat_variable_name %>.<%= message_table_name %>.build
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def set_<%= chat_variable_name %>
|
|
29
|
+
@<%= chat_variable_name %> = <%= chat_model_name %>.find(params[:id])
|
|
30
|
+
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
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
class <%= message_controller_class_name %> < ApplicationController
|
|
2
|
+
before_action :set_<%= chat_variable_name %>
|
|
3
|
+
|
|
4
|
+
def create
|
|
5
|
+
return unless content.present?
|
|
6
|
+
|
|
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 %> }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
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
|
+
end
|
|
20
|
+
|
|
21
|
+
def content
|
|
22
|
+
params[:<%= message_variable_name %>][:content]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
class <%= model_controller_class_name %> < ApplicationController
|
|
2
|
+
def index
|
|
3
|
+
@<%= model_table_name %> = <%= model_model_name %>.all
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def show
|
|
7
|
+
@<%= model_variable_name %> = <%= model_model_name %>.find(params[:id])
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def refresh
|
|
11
|
+
<%= model_model_name %>.refresh!
|
|
12
|
+
redirect_to <%= model_table_name %>_path, notice: "<%= model_model_name.pluralize %> refreshed successfully"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
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
|
+
|
|
5
|
+
<%= chat_variable_name %>.ask(content) do |chunk|
|
|
6
|
+
if chunk.content && !chunk.content.blank?
|
|
7
|
+
<%= message_variable_name %> = <%= chat_variable_name %>.<%= message_table_name %>.last
|
|
8
|
+
<%= message_variable_name %>.broadcast_append_chunk(chunk.content)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div id="<%%= dom_id <%= chat_model_name.demodulize.underscore %> %>">
|
|
2
|
+
<div>
|
|
3
|
+
<strong>Model:</strong>
|
|
4
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= model_table_name.singularize %>&.name || 'Default' %>
|
|
5
|
+
</div>
|
|
6
|
+
|
|
7
|
+
<div>
|
|
8
|
+
<strong>Messages:</strong>
|
|
9
|
+
<%%= <%= chat_model_name.demodulize.underscore %>.<%= message_table_name %>.count %>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div>
|
|
13
|
+
<strong>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,29 @@
|
|
|
1
|
+
<%%= form_with(model: <%= chat_variable_name %>, url: <%= chat_table_name %>_path) do |form| %>
|
|
2
|
+
<%% if <%= chat_variable_name %>.errors.any? %>
|
|
3
|
+
<div style="color: red">
|
|
4
|
+
<h2><%%= pluralize(<%= chat_variable_name %>.errors.count, "error") %> prohibited this <%= chat_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
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>
|
|
15
|
+
<%%= form.label :model, "Select AI model:", style: "display: block" %>
|
|
16
|
+
<%%= form.select :model,
|
|
17
|
+
options_for_select(<%= model_model_name %>.pluck(:name, :model_id).unshift(["Default (#{RubyLLM.config.default_model})", nil]), @selected_model),
|
|
18
|
+
{},
|
|
19
|
+
style: "width: 100%; max-width: 600px; padding: 5px;" %>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div style="margin-top: 15px;">
|
|
23
|
+
<%%= form.text_field :prompt, style: "width: 100%; max-width: 600px;", placeholder: "What would you like to discuss?", autofocus: true %>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div>
|
|
27
|
+
<%%= form.submit "Start new <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
28
|
+
</div>
|
|
29
|
+
<%% end %>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<p style="color: green"><%%= notice %></p>
|
|
2
|
+
|
|
3
|
+
<%% content_for :title, "<%= chat_model_name.pluralize %>" %>
|
|
4
|
+
|
|
5
|
+
<h1><%= chat_model_name.pluralize %></h1>
|
|
6
|
+
|
|
7
|
+
<div id="<%= chat_table_name %>">
|
|
8
|
+
<%% @<%= chat_table_name %>.each do |<%= chat_variable_name %>| %>
|
|
9
|
+
<%%= render <%= chat_variable_name %> %>
|
|
10
|
+
<p>
|
|
11
|
+
<%%= link_to "Show this <%= chat_table_name.singularize.humanize.downcase %>", <%= chat_variable_name %> %>
|
|
12
|
+
</p>
|
|
13
|
+
<%% end %>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<%%= link_to "New <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path %>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<%% content_for :title, "New <%= chat_table_name.singularize.humanize.downcase %>" %>
|
|
2
|
+
|
|
3
|
+
<h1>New <%= chat_table_name.singularize.humanize.downcase %></h1>
|
|
4
|
+
|
|
5
|
+
<%%= render "form", <%= chat_variable_name %>: @<%= chat_variable_name %> %>
|
|
6
|
+
|
|
7
|
+
<br>
|
|
8
|
+
|
|
9
|
+
<div>
|
|
10
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<p style="color: green"><%%= notice %></p>
|
|
2
|
+
|
|
3
|
+
<%%= turbo_stream_from "<%= chat_variable_name %>_#{@<%= chat_variable_name %>.id}" %>
|
|
4
|
+
|
|
5
|
+
<%% content_for :title, "<%= chat_model_name %>" %>
|
|
6
|
+
|
|
7
|
+
<h1><%= chat_model_name %> <%%= @<%= chat_variable_name %>.id %></h1>
|
|
8
|
+
|
|
9
|
+
<p>Using <strong><%%= @<%= chat_variable_name %>.<%= model_table_name.singularize %>.name %></strong></p>
|
|
10
|
+
|
|
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
|
+
<%% end %>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div style="margin-top: 30px;">
|
|
18
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/form", <%= chat_variable_name %>: @<%= chat_variable_name %>, <%= message_variable_name %>: @<%= message_variable_name %> %>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div style="margin-top: 20px;">
|
|
22
|
+
<%%= link_to "Back to <%= chat_table_name.humanize.downcase %>", <%= chat_table_name %>_path %>
|
|
23
|
+
</div>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<%%= content %>
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
<div style="color: red">
|
|
4
|
+
<h2><%%= pluralize(<%= message_variable_name %>.errors.count, "error") %> prohibited this <%= message_table_name.singularize.humanize.downcase %> from being saved:</h2>
|
|
5
|
+
|
|
6
|
+
<ul>
|
|
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>
|
|
15
|
+
<%%= form.text_field :content, style: "width: 100%; max-width: 600px;", placeholder: "Message...", autofocus: true %>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div>
|
|
19
|
+
<%%= form.submit "Send <%= message_table_name.singularize.humanize.downcase %>" %>
|
|
20
|
+
</div>
|
|
21
|
+
<%% end %>
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
<div style="font-weight: bold; margin-bottom: 5px;">
|
|
4
|
+
<%%= <%= message_model_name.demodulize.underscore %>.role&.capitalize %>
|
|
5
|
+
</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 %>
|
|
10
|
+
<div style="font-size: 0.85em; color: #666; margin-top: 5px;">
|
|
11
|
+
<%%= <%= message_model_name.demodulize.underscore %>.created_at&.strftime("%I:%M %p") %>
|
|
12
|
+
</div>
|
|
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>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<%%= turbo_stream.append "<%= message_table_name %>" do %>
|
|
2
|
+
<%% @<%= chat_variable_name %>.<%= message_table_name %>.last(2).each do |<%= message_variable_name %>| %>
|
|
3
|
+
<%%= render <%= message_variable_name %> %>
|
|
4
|
+
<%% end %>
|
|
5
|
+
<%% end %>
|
|
6
|
+
|
|
7
|
+
<%%= turbo_stream.replace "new_<%= message_variable_name %>" do %>
|
|
8
|
+
<%%= render "<%= message_model_name.underscore.pluralize %>/form", <%= chat_variable_name %>: @<%= chat_variable_name %>, <%= message_variable_name %>: @<%= chat_variable_name %>.<%= message_table_name %>.build %>
|
|
9
|
+
<%% end %>
|