rails_chatbot 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +284 -0
- data/Rakefile +8 -0
- data/app/assets/stylesheets/rails_chatbot/application.css +15 -0
- data/app/controllers/rails_chatbot/application_controller.rb +14 -0
- data/app/controllers/rails_chatbot/chat_controller.rb +27 -0
- data/app/controllers/rails_chatbot/conversations_controller.rb +48 -0
- data/app/controllers/rails_chatbot/messages_controller.rb +55 -0
- data/app/helpers/rails_chatbot/application_helper.rb +12 -0
- data/app/javascript/rails_chatbot/application.js +163 -0
- data/app/jobs/rails_chatbot/application_job.rb +4 -0
- data/app/mailers/rails_chatbot/application_mailer.rb +6 -0
- data/app/models/rails_chatbot/application_record.rb +5 -0
- data/app/models/rails_chatbot/conversation.rb +25 -0
- data/app/models/rails_chatbot/knowledge_base.rb +57 -0
- data/app/models/rails_chatbot/message.rb +13 -0
- data/app/services/rails_chatbot/chat_service.rb +41 -0
- data/app/services/rails_chatbot/knowledge_retrieval_service.rb +42 -0
- data/app/services/rails_chatbot/llm_service.rb +64 -0
- data/app/views/layouts/rails_chatbot/application.html.erb +17 -0
- data/app/views/rails_chatbot/chat/index.html.erb +165 -0
- data/config/initializers/rails_chatbot.rb +13 -0
- data/config/routes.rb +13 -0
- data/db/migrate/001_create_rails_chatbot_conversations.rb +10 -0
- data/db/migrate/002_create_rails_chatbot_messages.rb +11 -0
- data/db/migrate/003_create_rails_chatbot_knowledge_bases.rb +16 -0
- data/db/seeds.rb +86 -0
- data/lib/generators/rails_chatbot/install/install_generator.rb +19 -0
- data/lib/generators/rails_chatbot/install/templates/README +16 -0
- data/lib/generators/rails_chatbot/install/templates/rails_chatbot.rb +13 -0
- data/lib/rails_chatbot/engine.rb +19 -0
- data/lib/rails_chatbot/knowledge_indexer.rb +71 -0
- data/lib/rails_chatbot/railtie.rb +22 -0
- data/lib/rails_chatbot/version.rb +3 -0
- data/lib/rails_chatbot.rb +29 -0
- data/lib/tasks/rails_chatbot_tasks.rake +81 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 13b20ee2717e914aea6e5634f28cee026b971f5b6ec3216f70e6cd48a860159a
|
|
4
|
+
data.tar.gz: 288ca5976edf919441a32264401b7fa26ea249f25de530d9b0319b8b937ba897
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: ddfdc4e42f4c3bf733f002232879fb96d7a7bfecd8e1a9b1319497d0ded151c8c91853773f10f085f88ffdd1e13b5afc82213865b2d4d450b960cc3a75d1cb46
|
|
7
|
+
data.tar.gz: 05167d520483a23de9df8153f0b5e459e5986e0a6fde86940e5082b6b4b567771eb1433c6498ed2a656c6a1ef51510e5323194ff2d81dbc6e174df687502f9af
|
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# RailsChatbot
|
|
2
|
+
|
|
3
|
+
A powerful Rails engine gem that provides an intelligent chatbot system with knowledge base integration for your Ruby on Rails application. The chatbot can answer questions about your application by indexing your models and content.
|
|
4
|
+
|
|
5
|
+
## � Quick Start Guide
|
|
6
|
+
|
|
7
|
+
### Step 1: Install the Gem
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem "rails_chatbot"
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Then execute:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ bundle install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Step 2: Run Migrations
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
$ rails rails_chatbot:install:migrations
|
|
25
|
+
$ rails db:migrate
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Step 3: Configure OpenAI
|
|
29
|
+
|
|
30
|
+
Create `config/initializers/rails_chatbot.rb`:
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
RailsChatbot.configure do |config|
|
|
34
|
+
config.openai_api_key = ENV['OPENAI_API_KEY'] # Required
|
|
35
|
+
config.openai_model = 'gpt-4o-mini' # Optional
|
|
36
|
+
config.chatbot_title = 'My App Assistant' # Optional
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Set your OpenAI API key:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
export OPENAI_API_KEY=your_openai_api_key_here
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Step 4: Mount the Engine
|
|
47
|
+
|
|
48
|
+
Add to your `config/routes.rb`:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
Rails.application.routes.draw do
|
|
52
|
+
mount RailsChatbot::Engine => "/chatbot"
|
|
53
|
+
|
|
54
|
+
# Your other routes...
|
|
55
|
+
end
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 5: Start Your Server
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
rails server
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Visit `http://localhost:3000/chatbot` to see your chatbot!
|
|
65
|
+
|
|
66
|
+
## 🧪 Testing Your Chatbot
|
|
67
|
+
|
|
68
|
+
### 1. Test Basic Chat
|
|
69
|
+
- Open `http://localhost:3000/chatbot`
|
|
70
|
+
- Type "Hello" and send a message
|
|
71
|
+
- The chatbot should respond
|
|
72
|
+
|
|
73
|
+
### 2. Add Knowledge Base Content
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
# Add custom knowledge
|
|
77
|
+
rails runner "RailsChatbot::KnowledgeIndexer.add_custom_knowledge(
|
|
78
|
+
title: 'User Registration',
|
|
79
|
+
content: 'Users can register by clicking the Sign Up button...',
|
|
80
|
+
source_type: 'help'
|
|
81
|
+
)"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 3. Test Knowledge Search
|
|
85
|
+
In the chat, try asking:
|
|
86
|
+
- "How do users register?"
|
|
87
|
+
- "What features are available?"
|
|
88
|
+
- "Tell me about user management"
|
|
89
|
+
|
|
90
|
+
### 4. Test API Endpoints
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# Test search
|
|
94
|
+
curl "http://localhost:3000/chatbot/search?q=user"
|
|
95
|
+
|
|
96
|
+
# Test conversation creation
|
|
97
|
+
curl -X POST "http://localhost:3000/chatbot/conversations" \
|
|
98
|
+
-H "Content-Type: application/json" \
|
|
99
|
+
-d '{}'
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 5. Check Knowledge Base Stats
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
rake app:rails_chatbot:stats
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 📚 Advanced Usage
|
|
109
|
+
|
|
110
|
+
### Index Your Models
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# Index specific models
|
|
114
|
+
rake app:rails_chatbot:index_models[User,Post,Product]
|
|
115
|
+
|
|
116
|
+
# Index all models
|
|
117
|
+
rake app:rails_chatbot:index_all
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Add Custom Knowledge
|
|
121
|
+
|
|
122
|
+
```ruby
|
|
123
|
+
# Via code
|
|
124
|
+
RailsChatbot::KnowledgeBase.create!(
|
|
125
|
+
title: "How to Reset Password",
|
|
126
|
+
content: "Click 'Forgot Password' on the login page...",
|
|
127
|
+
source_type: "help"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Via rake task
|
|
131
|
+
rake app:rails_chatbot:add_knowledge['Title','Content','Type']
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Customize Configuration
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
RailsChatbot.configure do |config|
|
|
138
|
+
config.openai_api_key = ENV['OPENAI_API_KEY']
|
|
139
|
+
config.openai_model = 'gpt-4o-mini'
|
|
140
|
+
config.chatbot_title = 'Support Assistant'
|
|
141
|
+
config.current_user_proc = proc { |controller| controller.current_user }
|
|
142
|
+
config.indexable_models = [User, Product, Order] # Custom models to index
|
|
143
|
+
end
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🔧 Management Commands
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Knowledge base management
|
|
150
|
+
rake app:rails_chatbot:stats # View statistics
|
|
151
|
+
rake app:rails_chatbot:clear_knowledge_base # Clear all entries
|
|
152
|
+
rake app:rails_chatbot:add_knowledge['Title','Content','Type'] # Add knowledge
|
|
153
|
+
rake app:rails_chatbot:index_all # Index all models
|
|
154
|
+
rake app:rails_chatbot:index_models[User,Post] # Index specific models
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 🎯 Common Use Cases
|
|
158
|
+
|
|
159
|
+
### E-commerce Site
|
|
160
|
+
```ruby
|
|
161
|
+
# Index products
|
|
162
|
+
rake app:rails_chatbot:index_models[Product,Category]
|
|
163
|
+
|
|
164
|
+
# Add help content
|
|
165
|
+
RailsChatbot::KnowledgeIndexer.add_custom_knowledge(
|
|
166
|
+
title: "Shipping Policy",
|
|
167
|
+
content: "We ship within 3-5 business days...",
|
|
168
|
+
source_type: "policy"
|
|
169
|
+
)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### SaaS Application
|
|
173
|
+
```ruby
|
|
174
|
+
# Index user models and features
|
|
175
|
+
rake app:rails_chatbot:index_models[User,Feature,Subscription]
|
|
176
|
+
|
|
177
|
+
# Add feature documentation
|
|
178
|
+
RailsChatbot::KnowledgeIndexer.add_custom_knowledge(
|
|
179
|
+
title: "Dashboard Overview",
|
|
180
|
+
content: "The dashboard shows your usage statistics...",
|
|
181
|
+
source_type: "documentation"
|
|
182
|
+
)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 🐛 Troubleshooting
|
|
186
|
+
|
|
187
|
+
### Common Issues
|
|
188
|
+
|
|
189
|
+
1. **OpenAI API Errors**
|
|
190
|
+
- Check your API key is valid
|
|
191
|
+
- Verify you have credits in your OpenAI account
|
|
192
|
+
- Try a different model (gpt-3.5-turbo)
|
|
193
|
+
|
|
194
|
+
2. **No Knowledge Found**
|
|
195
|
+
- Run `rake app:rails_chatbot:index_all` to populate knowledge base
|
|
196
|
+
- Add custom knowledge entries
|
|
197
|
+
- Check `rake app:rails_chatbot:stats` for entries
|
|
198
|
+
|
|
199
|
+
3. **Search Not Working**
|
|
200
|
+
- Ensure PostgreSQL is configured
|
|
201
|
+
- Check database migrations ran successfully
|
|
202
|
+
- Verify knowledge base has content
|
|
203
|
+
|
|
204
|
+
4. **Routing Issues**
|
|
205
|
+
- Confirm engine is mounted in routes.rb
|
|
206
|
+
- Check for route conflicts with existing paths
|
|
207
|
+
- Restart Rails server after route changes
|
|
208
|
+
|
|
209
|
+
### Debug Mode
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
# In development, add to initializer
|
|
213
|
+
RailsChatbot.configure do |config|
|
|
214
|
+
# ... other config
|
|
215
|
+
Rails.logger.level = :debug
|
|
216
|
+
end
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## 📱 API Reference
|
|
220
|
+
|
|
221
|
+
### Endpoints
|
|
222
|
+
|
|
223
|
+
- `GET /chatbot` - Chat interface
|
|
224
|
+
- `POST /chatbot/conversations` - Create conversation
|
|
225
|
+
- `GET /chatbot/conversations` - List conversations
|
|
226
|
+
- `POST /chatbot/conversations/:id/messages` - Send message
|
|
227
|
+
- `GET /chatbot/search?q=query` - Search knowledge
|
|
228
|
+
|
|
229
|
+
### Response Format
|
|
230
|
+
|
|
231
|
+
```json
|
|
232
|
+
{
|
|
233
|
+
"message": {
|
|
234
|
+
"role": "assistant",
|
|
235
|
+
"content": "Here's the answer...",
|
|
236
|
+
"created_at": "2026-02-12T12:00:00Z"
|
|
237
|
+
},
|
|
238
|
+
"knowledge_sources": [
|
|
239
|
+
{
|
|
240
|
+
"title": "User Guide",
|
|
241
|
+
"source_type": "documentation",
|
|
242
|
+
"source_url": "/docs/users"
|
|
243
|
+
}
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## 🎨 Customization
|
|
249
|
+
|
|
250
|
+
### Override Views
|
|
251
|
+
|
|
252
|
+
Create `app/views/rails_chatbot/chat/index.html.erb` in your app to customize the chat interface.
|
|
253
|
+
|
|
254
|
+
### Custom Styles
|
|
255
|
+
|
|
256
|
+
Add to `app/assets/stylesheets/rails_chatbot/custom.css`:
|
|
257
|
+
|
|
258
|
+
```css
|
|
259
|
+
.chatbot-container {
|
|
260
|
+
background: your-brand-color;
|
|
261
|
+
border-radius: your-preference;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Custom JavaScript
|
|
266
|
+
|
|
267
|
+
Extend functionality in `app/javascript/rails_chatbot/custom.js`.
|
|
268
|
+
|
|
269
|
+
## 📋 Requirements
|
|
270
|
+
|
|
271
|
+
- Ruby on Rails 8.0.4 or higher
|
|
272
|
+
- PostgreSQL (for full-text search)
|
|
273
|
+
- OpenAI API key
|
|
274
|
+
- Modern web browser
|
|
275
|
+
|
|
276
|
+
## 📄 License
|
|
277
|
+
|
|
278
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
279
|
+
|
|
280
|
+
## 🤝 Support
|
|
281
|
+
|
|
282
|
+
- 📖 [Documentation](https://github.com/yourusername/rails_chatbot)
|
|
283
|
+
- 🐛 [Issues](https://github.com/yourusername/rails_chatbot/issues)
|
|
284
|
+
- 💬 [Discussions](https://github.com/yourusername/rails_chatbot/discussions)
|
data/Rakefile
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
|
3
|
+
* listed below.
|
|
4
|
+
*
|
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
|
7
|
+
*
|
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
|
|
10
|
+
* files in this directory. Styles in this file should be added after the last require_* statement.
|
|
11
|
+
* It is generally better to create a new file per style scope.
|
|
12
|
+
*
|
|
13
|
+
*= require_tree .
|
|
14
|
+
*= require_self
|
|
15
|
+
*/
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module RailsChatbot
|
|
2
|
+
class ApplicationController < ActionController::Base
|
|
3
|
+
protect_from_forgery with: :exception, if: -> { request.format.html? }
|
|
4
|
+
skip_before_action :verify_authenticity_token, if: -> { request.format.json? }
|
|
5
|
+
|
|
6
|
+
private
|
|
7
|
+
|
|
8
|
+
def current_user
|
|
9
|
+
# Override this method in your main application controller
|
|
10
|
+
# or define it in an initializer
|
|
11
|
+
instance_eval(&Rails.application.config.rails_chatbot.current_user_proc) if Rails.application.config.rails_chatbot&.current_user_proc
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module RailsChatbot
|
|
2
|
+
class ChatController < ApplicationController
|
|
3
|
+
def index
|
|
4
|
+
# Main chat interface
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
def search
|
|
8
|
+
query = params[:q].to_s.strip
|
|
9
|
+
return render json: { results: [] } if query.blank?
|
|
10
|
+
|
|
11
|
+
service = KnowledgeRetrievalService.new(query: query, limit: 10)
|
|
12
|
+
results = service.retrieve
|
|
13
|
+
|
|
14
|
+
render json: {
|
|
15
|
+
results: results.map do |result|
|
|
16
|
+
{
|
|
17
|
+
title: result[:title],
|
|
18
|
+
content: result[:content].truncate(200),
|
|
19
|
+
source_type: result[:source_type],
|
|
20
|
+
source_id: result[:source_id],
|
|
21
|
+
source_url: result[:source_url]
|
|
22
|
+
}
|
|
23
|
+
end
|
|
24
|
+
}
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module RailsChatbot
|
|
2
|
+
class ConversationsController < ApplicationController
|
|
3
|
+
before_action :set_conversation, only: [:show, :destroy]
|
|
4
|
+
|
|
5
|
+
def index
|
|
6
|
+
@conversations = Conversation.where(session_id: session_id)
|
|
7
|
+
.recent
|
|
8
|
+
.limit(20)
|
|
9
|
+
render json: @conversations.map { |c| { id: c.id, title: c.title, created_at: c.created_at } }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def show
|
|
13
|
+
@messages = @conversation.messages.order(created_at: :asc)
|
|
14
|
+
render json: {
|
|
15
|
+
conversation: {
|
|
16
|
+
id: @conversation.id,
|
|
17
|
+
title: @conversation.title,
|
|
18
|
+
created_at: @conversation.created_at
|
|
19
|
+
},
|
|
20
|
+
messages: @messages.map { |m| { role: m.role, content: m.content, created_at: m.created_at } }
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create
|
|
25
|
+
@conversation = Conversation.find_or_create_by(session_id: session_id) do |conv|
|
|
26
|
+
conv.title = params[:title] || "Conversation #{Time.current.strftime('%Y-%m-%d %H:%M')}"
|
|
27
|
+
conv.user = current_user if respond_to?(:current_user) && current_user
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
render json: { conversation_id: @conversation.id, title: @conversation.title }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def destroy
|
|
34
|
+
@conversation.destroy
|
|
35
|
+
head :no_content
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def set_conversation
|
|
41
|
+
@conversation = Conversation.find_by!(id: params[:id], session_id: session_id)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def session_id
|
|
45
|
+
@session_id ||= session[:chatbot_session_id] ||= SecureRandom.uuid
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module RailsChatbot
|
|
2
|
+
class MessagesController < ApplicationController
|
|
3
|
+
before_action :set_conversation
|
|
4
|
+
|
|
5
|
+
def create
|
|
6
|
+
user_message = params[:message].to_s.strip
|
|
7
|
+
|
|
8
|
+
if user_message.blank?
|
|
9
|
+
render json: { error: 'Message cannot be blank' }, status: :unprocessable_entity
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
chat_service = ChatService.new(conversation: @conversation)
|
|
14
|
+
result = chat_service.process_message(user_message)
|
|
15
|
+
|
|
16
|
+
render json: {
|
|
17
|
+
message: {
|
|
18
|
+
role: 'assistant',
|
|
19
|
+
content: result[:response],
|
|
20
|
+
created_at: Time.current
|
|
21
|
+
},
|
|
22
|
+
knowledge_sources: result[:knowledge_sources].map do |source|
|
|
23
|
+
{
|
|
24
|
+
title: source[:title],
|
|
25
|
+
source_type: source[:source_type],
|
|
26
|
+
source_id: source[:source_id],
|
|
27
|
+
source_url: source[:source_url]
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
}
|
|
31
|
+
rescue => e
|
|
32
|
+
Rails.logger.error("Chat error: #{e.message}\n#{e.backtrace.join("\n")}")
|
|
33
|
+
render json: { error: 'An error occurred while processing your message' }, status: :internal_server_error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def set_conversation
|
|
39
|
+
conversation_id = params[:conversation_id] || params[:conversation][:id] rescue nil
|
|
40
|
+
|
|
41
|
+
if conversation_id
|
|
42
|
+
@conversation = Conversation.find_by!(id: conversation_id, session_id: session_id)
|
|
43
|
+
else
|
|
44
|
+
@conversation = Conversation.find_or_create_by(session_id: session_id) do |conv|
|
|
45
|
+
conv.title = "Conversation #{Time.current.strftime('%Y-%m-%d %H:%M')}"
|
|
46
|
+
conv.user = current_user if respond_to?(:current_user) && current_user
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def session_id
|
|
52
|
+
@session_id ||= session[:chatbot_session_id] ||= SecureRandom.uuid
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module RailsChatbot
|
|
2
|
+
module ApplicationHelper
|
|
3
|
+
def rails_chatbot_routes
|
|
4
|
+
routes = {
|
|
5
|
+
conversations_path: rails_chatbot.conversations_path,
|
|
6
|
+
messages_path: rails_chatbot.messages_path,
|
|
7
|
+
conversation_messages_path_template: rails_chatbot.conversation_messages_path(':id')
|
|
8
|
+
}
|
|
9
|
+
routes.to_json.html_safe
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
// Chatbot controller using Stimulus (or vanilla JS)
|
|
2
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
3
|
+
const chatbotContainer = document.querySelector('[data-controller="chatbot"]');
|
|
4
|
+
if (!chatbotContainer) return;
|
|
5
|
+
|
|
6
|
+
const messagesContainer = chatbotContainer.querySelector('[data-chatbot-target="messages"]');
|
|
7
|
+
const input = chatbotContainer.querySelector('[data-chatbot-target="input"]');
|
|
8
|
+
const sendButton = chatbotContainer.querySelector('[data-chatbot-target="sendButton"]');
|
|
9
|
+
let conversationId = null;
|
|
10
|
+
let isLoading = false;
|
|
11
|
+
|
|
12
|
+
// Initialize conversation
|
|
13
|
+
function initializeConversation() {
|
|
14
|
+
fetch(window.RailsChatbot.routes.conversationsPath(), {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({})
|
|
21
|
+
})
|
|
22
|
+
.then(response => response.json())
|
|
23
|
+
.then(data => {
|
|
24
|
+
conversationId = data.conversation_id;
|
|
25
|
+
})
|
|
26
|
+
.catch(error => console.error('Error initializing conversation:', error));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Add message to UI
|
|
30
|
+
function addMessage(role, content, knowledgeSources = []) {
|
|
31
|
+
const messageDiv = document.createElement('div');
|
|
32
|
+
messageDiv.className = `message ${role}`;
|
|
33
|
+
|
|
34
|
+
const bubble = document.createElement('div');
|
|
35
|
+
bubble.className = 'message-bubble';
|
|
36
|
+
bubble.textContent = content;
|
|
37
|
+
|
|
38
|
+
messageDiv.appendChild(bubble);
|
|
39
|
+
|
|
40
|
+
if (knowledgeSources && knowledgeSources.length > 0) {
|
|
41
|
+
const sourcesDiv = document.createElement('div');
|
|
42
|
+
sourcesDiv.className = 'knowledge-sources';
|
|
43
|
+
sourcesDiv.innerHTML = 'Sources: ' + knowledgeSources.map(s => {
|
|
44
|
+
if (s.source_url) {
|
|
45
|
+
return `<a href="${s.source_url}" target="_blank">${s.title}</a>`;
|
|
46
|
+
}
|
|
47
|
+
return s.title;
|
|
48
|
+
}).join(', ');
|
|
49
|
+
messageDiv.appendChild(sourcesDiv);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const timeDiv = document.createElement('div');
|
|
53
|
+
timeDiv.className = 'message-time';
|
|
54
|
+
timeDiv.textContent = new Date().toLocaleTimeString();
|
|
55
|
+
messageDiv.appendChild(timeDiv);
|
|
56
|
+
|
|
57
|
+
messagesContainer.appendChild(messageDiv);
|
|
58
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Show loading indicator
|
|
62
|
+
function showLoading() {
|
|
63
|
+
const loadingDiv = document.createElement('div');
|
|
64
|
+
loadingDiv.className = 'message assistant';
|
|
65
|
+
loadingDiv.id = 'loading-message';
|
|
66
|
+
loadingDiv.innerHTML = '<div class="message-bubble"><div class="loading"></div></div>';
|
|
67
|
+
messagesContainer.appendChild(loadingDiv);
|
|
68
|
+
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Remove loading indicator
|
|
72
|
+
function removeLoading() {
|
|
73
|
+
const loading = document.getElementById('loading-message');
|
|
74
|
+
if (loading) loading.remove();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Send message
|
|
78
|
+
function sendMessage() {
|
|
79
|
+
const message = input.value.trim();
|
|
80
|
+
if (!message || isLoading) return;
|
|
81
|
+
|
|
82
|
+
// Add user message to UI
|
|
83
|
+
addMessage('user', message);
|
|
84
|
+
input.value = '';
|
|
85
|
+
isLoading = true;
|
|
86
|
+
sendButton.disabled = true;
|
|
87
|
+
showLoading();
|
|
88
|
+
|
|
89
|
+
// Determine endpoint
|
|
90
|
+
const url = conversationId
|
|
91
|
+
? window.RailsChatbot.routes.conversationMessagesPath(conversationId)
|
|
92
|
+
: window.RailsChatbot.routes.messagesPath();
|
|
93
|
+
|
|
94
|
+
// Send to server
|
|
95
|
+
fetch(url, {
|
|
96
|
+
method: 'POST',
|
|
97
|
+
headers: {
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || ''
|
|
100
|
+
},
|
|
101
|
+
body: JSON.stringify({ message: message })
|
|
102
|
+
})
|
|
103
|
+
.then(response => response.json())
|
|
104
|
+
.then(data => {
|
|
105
|
+
removeLoading();
|
|
106
|
+
|
|
107
|
+
if (data.error) {
|
|
108
|
+
addMessage('assistant', `Error: ${data.error}`);
|
|
109
|
+
} else {
|
|
110
|
+
if (!conversationId && data.conversation_id) {
|
|
111
|
+
conversationId = data.conversation_id;
|
|
112
|
+
}
|
|
113
|
+
addMessage('assistant', data.message.content, data.knowledge_sources || []);
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
.catch(error => {
|
|
117
|
+
removeLoading();
|
|
118
|
+
addMessage('assistant', 'Sorry, there was an error processing your message. Please try again.');
|
|
119
|
+
console.error('Error:', error);
|
|
120
|
+
})
|
|
121
|
+
.finally(() => {
|
|
122
|
+
isLoading = false;
|
|
123
|
+
sendButton.disabled = false;
|
|
124
|
+
input.focus();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Handle key press
|
|
129
|
+
function handleKeyDown(event) {
|
|
130
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
sendMessage();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Attach event listeners
|
|
137
|
+
sendButton.addEventListener('click', sendMessage);
|
|
138
|
+
input.addEventListener('keydown', handleKeyDown);
|
|
139
|
+
|
|
140
|
+
// Initialize conversation on load
|
|
141
|
+
initializeConversation();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Routes helper - populated from server
|
|
145
|
+
if (window.RailsChatbot && window.RailsChatbot.routes) {
|
|
146
|
+
const routes = typeof window.RailsChatbot.routes === 'string'
|
|
147
|
+
? JSON.parse(window.RailsChatbot.routes)
|
|
148
|
+
: window.RailsChatbot.routes;
|
|
149
|
+
|
|
150
|
+
window.RailsChatbot.routes = {
|
|
151
|
+
conversationsPath: () => routes.conversations_path,
|
|
152
|
+
conversationMessagesPath: (id) => routes.conversation_messages_path_template.replace(':id', id),
|
|
153
|
+
messagesPath: () => routes.messages_path
|
|
154
|
+
};
|
|
155
|
+
} else {
|
|
156
|
+
// Fallback routes
|
|
157
|
+
window.RailsChatbot = window.RailsChatbot || {};
|
|
158
|
+
window.RailsChatbot.routes = {
|
|
159
|
+
conversationsPath: () => '/rails_chatbot/conversations',
|
|
160
|
+
conversationMessagesPath: (id) => `/rails_chatbot/conversations/${id}/messages`,
|
|
161
|
+
messagesPath: () => '/rails_chatbot/messages'
|
|
162
|
+
};
|
|
163
|
+
}
|