rails_chatbot 0.1.1 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 981a8853491b0793ee73520f15615a11b9c05f0aa86151548df6b23e73807a44
4
- data.tar.gz: 79b7cee00596db1a4a2eda523090f28abf67187cde121aec1b8f2503986cca40
3
+ metadata.gz: c20ad83f73c15bb9cbed232cb5a2425aeef5ab8f430892cac4cf342b57fe3135
4
+ data.tar.gz: ea50ffd6cd98868149cbd3519637bd0502af3a244313f654d8f0f636f9710cfd
5
5
  SHA512:
6
- metadata.gz: 4cc44118e0480afb01980775cc01e5c6677b6468d3f0a287f2d510aecaf887e62056dd1be8e3a8be461b37cd9a2ed7e8e26bf6889c810e775dbfaa810e591b69
7
- data.tar.gz: 668f95682cd334a946df1d2edb3cb075544e0e2d1580dc2ad113249b55c89cc4ce9993b16cc88d97e2e314e90c6d608dc65e9003d75837cda52c97d37c3067c7
6
+ metadata.gz: 44d85729a3c0f7c20a593f06828dbae018b6efaa4e1fa604d1c631143458e9f46598a82b22d35a6a49eecef58caa8cb6a9f578b9cf1e9849ef351a3632fa7977
7
+ data.tar.gz: cac2f5c9ba771535c109707a3fa5289a4467bb89994de80772d15009984e8abb8b0483ac321a1e7a0dabe9e9efee0b203539602df6e0a3cc7e97335a3f5dafbe
data/README.md CHANGED
@@ -55,13 +55,30 @@ Rails.application.routes.draw do
55
55
  end
56
56
  ```
57
57
 
58
- ### Step 5: Start Your Server
58
+ ### Step 5: Enable the Widget (Optional)
59
+
60
+ To enable the floating chatbot widget, add this to your application layout:
61
+
62
+ ```erb
63
+ <%# In app/views/layouts/application.html.erb %>
64
+ <%= RailsChatbot::ApplicationHelper.new.include_chatbot_widget if RailsChatbot::ApplicationHelper.new.chatbot_widget_enabled? %>
65
+ ```
66
+
67
+ Or simply add to any view:
68
+
69
+ ```erb
70
+ <%= include_chatbot_widget %>
71
+ ```
72
+
73
+ The widget will automatically appear in the bottom-right corner of your screen.
74
+
75
+ ### Step 6: Start Your Server
59
76
 
60
77
  ```bash
61
78
  rails server
62
79
  ```
63
80
 
64
- Visit `http://localhost:3000/chatbot` to see your chatbot!
81
+ Visit `http://localhost:3000/chatbot` to see your chatbot, or look for the floating chat icon in the bottom-right corner of any page!
65
82
 
66
83
  ## 🧪 Testing Your Chatbot
67
84
 
@@ -280,7 +297,7 @@ For comprehensive documentation, visit our [Documentation Site](./docs/README.md
280
297
 
281
298
  ---
282
299
 
283
- ## �📋 Requirements
300
+ ## �� Requirements
284
301
 
285
302
  - Ruby on Rails 8.0.4 or higher
286
303
  - PostgreSQL (for full-text search)
@@ -0,0 +1,4 @@
1
+ //= link_tree ../images
2
+ //= link_directory ../stylesheets .css
3
+ //= link_tree ../../javascript .js
4
+ //= link_tree ../../../vendor/javascript .js
@@ -2,6 +2,11 @@ module RailsChatbot
2
2
  class ChatController < ApplicationController
3
3
  def index
4
4
  # Main chat interface
5
+ @rails_chatbot_routes = {
6
+ conversations_path: rails_chatbot.conversations_path,
7
+ conversation_messages_path_template: rails_chatbot.conversation_messages_path(':id'),
8
+ messages_path: rails_chatbot.messages_path
9
+ }.to_json
5
10
  end
6
11
 
7
12
  def search
@@ -8,5 +8,13 @@ module RailsChatbot
8
8
  }
9
9
  routes.to_json.html_safe
10
10
  end
11
+
12
+ def include_chatbot_widget
13
+ render 'rails_chatbot/shared/chat_widget'
14
+ end
15
+
16
+ def chatbot_widget_enabled?
17
+ RailsChatbot.configuration.enable_widget != false
18
+ end
11
19
  end
12
20
  end
@@ -1,11 +1,11 @@
1
- // Chatbot controller using Stimulus (or vanilla JS)
1
+ // Chatbot controller using vanilla JavaScript
2
2
  document.addEventListener('DOMContentLoaded', function() {
3
- const chatbotContainer = document.querySelector('[data-controller="chatbot"]');
3
+ const chatbotContainer = document.querySelector('.chatbot-container');
4
4
  if (!chatbotContainer) return;
5
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"]');
6
+ const messagesContainer = document.getElementById('chatbot-messages');
7
+ const input = document.getElementById('chatbot-input');
8
+ const sendButton = document.getElementById('chatbot-send');
9
9
  let conversationId = null;
10
10
  let isLoading = false;
11
11
 
@@ -156,8 +156,8 @@ if (window.RailsChatbot && window.RailsChatbot.routes) {
156
156
  // Fallback routes
157
157
  window.RailsChatbot = window.RailsChatbot || {};
158
158
  window.RailsChatbot.routes = {
159
- conversationsPath: () => '/rails_chatbot/conversations',
160
- conversationMessagesPath: (id) => `/rails_chatbot/conversations/${id}/messages`,
161
- messagesPath: () => '/rails_chatbot/messages'
159
+ conversationsPath: () => '/chatbot/conversations',
160
+ conversationMessagesPath: (id) => `/chatbot/conversations/${id}/messages`,
161
+ messagesPath: () => '/chatbot/messages'
162
162
  };
163
163
  }
@@ -0,0 +1,201 @@
1
+ // Chatbot Widget JavaScript
2
+ document.addEventListener('DOMContentLoaded', function() {
3
+ const chatbotIcon = document.getElementById('chatbot-toggle');
4
+ const chatbotWindow = document.getElementById('chatbot-window');
5
+ const chatbotClose = document.getElementById('chatbot-close');
6
+ const messagesContainer = document.getElementById('chatbot-messages');
7
+ const input = document.getElementById('chatbot-input');
8
+ const sendButton = document.getElementById('chatbot-send');
9
+ const unreadBadge = document.getElementById('unread-badge');
10
+
11
+ let conversationId = null;
12
+ let isLoading = false;
13
+ let isWindowOpen = false;
14
+
15
+ // Toggle chat window
16
+ function toggleChatWindow() {
17
+ isWindowOpen = !isWindowOpen;
18
+
19
+ if (isWindowOpen) {
20
+ chatbotWindow.classList.add('open');
21
+ unreadBadge.style.display = 'none';
22
+ input.focus();
23
+ } else {
24
+ chatbotWindow.classList.remove('open');
25
+ }
26
+ }
27
+
28
+ // Close chat window
29
+ function closeChatWindow() {
30
+ isWindowOpen = false;
31
+ chatbotWindow.classList.remove('open');
32
+ }
33
+
34
+ // Initialize conversation
35
+ function initializeConversation() {
36
+ fetch(window.RailsChatbot.routes.conversationsPath(), {
37
+ method: 'POST',
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || ''
41
+ },
42
+ body: JSON.stringify({})
43
+ })
44
+ .then(response => response.json())
45
+ .then(data => {
46
+ conversationId = data.conversation_id;
47
+ })
48
+ .catch(error => console.error('Error initializing conversation:', error));
49
+ }
50
+
51
+ // Add message to UI
52
+ function addMessage(role, content, knowledgeSources = []) {
53
+ const messageDiv = document.createElement('div');
54
+ messageDiv.className = `message ${role}`;
55
+
56
+ const bubble = document.createElement('div');
57
+ bubble.className = 'message-bubble';
58
+ bubble.textContent = content;
59
+
60
+ messageDiv.appendChild(bubble);
61
+
62
+ if (knowledgeSources && knowledgeSources.length > 0) {
63
+ const sourcesDiv = document.createElement('div');
64
+ sourcesDiv.className = 'knowledge-sources';
65
+ sourcesDiv.innerHTML = 'Sources: ' + knowledgeSources.map(s => {
66
+ if (s.source_url) {
67
+ return `<a href="${s.source_url}" target="_blank">${s.title}</a>`;
68
+ }
69
+ return s.title;
70
+ }).join(', ');
71
+ messageDiv.appendChild(sourcesDiv);
72
+ }
73
+
74
+ const timeDiv = document.createElement('div');
75
+ timeDiv.className = 'message-time';
76
+ timeDiv.textContent = new Date().toLocaleTimeString();
77
+ messageDiv.appendChild(timeDiv);
78
+
79
+ messagesContainer.appendChild(messageDiv);
80
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
81
+ }
82
+
83
+ // Show loading indicator
84
+ function showLoading() {
85
+ const loadingDiv = document.createElement('div');
86
+ loadingDiv.className = 'message assistant';
87
+ loadingDiv.id = 'loading-message';
88
+ loadingDiv.innerHTML = '<div class="message-bubble"><div class="loading"></div></div>';
89
+ messagesContainer.appendChild(loadingDiv);
90
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
91
+ }
92
+
93
+ // Remove loading indicator
94
+ function removeLoading() {
95
+ const loading = document.getElementById('loading-message');
96
+ if (loading) loading.remove();
97
+ }
98
+
99
+ // Show unread indicator
100
+ function showUnreadIndicator() {
101
+ if (!isWindowOpen) {
102
+ unreadBadge.style.display = 'flex';
103
+ }
104
+ }
105
+
106
+ // Send message
107
+ function sendMessage() {
108
+ const message = input.value.trim();
109
+ if (!message || isLoading) return;
110
+
111
+ // Add user message to UI
112
+ addMessage('user', message);
113
+ input.value = '';
114
+ isLoading = true;
115
+ sendButton.disabled = true;
116
+ showLoading();
117
+
118
+ // Determine endpoint
119
+ const url = conversationId
120
+ ? window.RailsChatbot.routes.conversationMessagesPath(conversationId)
121
+ : window.RailsChatbot.routes.messagesPath();
122
+
123
+ // Send to server
124
+ fetch(url, {
125
+ method: 'POST',
126
+ headers: {
127
+ 'Content-Type': 'application/json',
128
+ 'X-CSRF-Token': document.querySelector('meta[name="csrf-token"]')?.content || ''
129
+ },
130
+ body: JSON.stringify({ message: message })
131
+ })
132
+ .then(response => response.json())
133
+ .then(data => {
134
+ removeLoading();
135
+
136
+ if (data.error) {
137
+ addMessage('assistant', `Error: ${data.error}`);
138
+ } else {
139
+ if (!conversationId && data.conversation_id) {
140
+ conversationId = data.conversation_id;
141
+ }
142
+ addMessage('assistant', data.message.content, data.knowledge_sources || []);
143
+ showUnreadIndicator();
144
+ }
145
+ })
146
+ .catch(error => {
147
+ removeLoading();
148
+ addMessage('assistant', 'Sorry, there was an error processing your message. Please try again.');
149
+ console.error('Error:', error);
150
+ })
151
+ .finally(() => {
152
+ isLoading = false;
153
+ sendButton.disabled = false;
154
+ input.focus();
155
+ });
156
+ }
157
+
158
+ // Handle key press
159
+ function handleKeyDown(event) {
160
+ if (event.key === 'Enter' && !event.shiftKey) {
161
+ event.preventDefault();
162
+ sendMessage();
163
+ }
164
+ }
165
+
166
+ // Attach event listeners
167
+ if (chatbotIcon) chatbotIcon.addEventListener('click', toggleChatWindow);
168
+ if (chatbotClose) chatbotClose.addEventListener('click', closeChatWindow);
169
+ if (sendButton) sendButton.addEventListener('click', sendMessage);
170
+ if (input) input.addEventListener('keydown', handleKeyDown);
171
+
172
+ // Initialize conversation on load
173
+ initializeConversation();
174
+
175
+ // Show welcome message if no messages
176
+ const existingMessages = messagesContainer.querySelectorAll('.message');
177
+ if (existingMessages.length === 0) {
178
+ addMessage('assistant', 'Hello! I\'m your application assistant. How can I help you today?');
179
+ }
180
+ });
181
+
182
+ // Routes helper - populated from server
183
+ if (window.RailsChatbot && window.RailsChatbot.routes) {
184
+ const routes = typeof window.RailsChatbot.routes === 'string'
185
+ ? JSON.parse(window.RailsChatbot.routes)
186
+ : window.RailsChatbot.routes;
187
+
188
+ window.RailsChatbot.routes = {
189
+ conversationsPath: () => routes.conversations_path,
190
+ conversationMessagesPath: (id) => routes.conversation_messages_path_template.replace(':id', id),
191
+ messagesPath: () => routes.messages_path
192
+ };
193
+ } else {
194
+ // Fallback routes
195
+ window.RailsChatbot = window.RailsChatbot || {};
196
+ window.RailsChatbot.routes = {
197
+ conversationsPath: () => '/chatbot/conversations',
198
+ conversationMessagesPath: (id) => `/chatbot/conversations/${id}/messages`,
199
+ messagesPath: () => '/chatbot/messages'
200
+ };
201
+ }
@@ -0,0 +1,233 @@
1
+ module RailsChatbot
2
+ class ApplicationContentService
3
+ class << self
4
+ def index_application_content
5
+ return unless RailsChatbot.configuration.enable_knowledge_base_indexing
6
+
7
+ # Index common application files
8
+ index_readme_files
9
+ index_documentation_files
10
+ index_route_files
11
+ index_model_files
12
+ index_custom_content
13
+ end
14
+
15
+ def search_application_content(query)
16
+ # First search in knowledge base
17
+ knowledge_results = KnowledgeRetrievalService.new(query: query).retrieve
18
+
19
+ # If no results, try to match with predefined responses
20
+ if knowledge_results.empty?
21
+ return [find_default_response(query)]
22
+ end
23
+
24
+ knowledge_results
25
+ end
26
+
27
+ private
28
+
29
+ def index_readme_files
30
+ readme_paths = [
31
+ Rails.root.join('README.md'),
32
+ Rails.root.join('README.rdoc'),
33
+ Rails.root.join('doc', 'README.md')
34
+ ]
35
+
36
+ readme_paths.each do |path|
37
+ next unless File.exist?(path)
38
+
39
+ content = File.read(path)
40
+ add_knowledge_entry(
41
+ title: "Application Documentation",
42
+ content: extract_summary(content),
43
+ source_type: "documentation",
44
+ source_url: "/docs"
45
+ )
46
+ end
47
+ end
48
+
49
+ def index_documentation_files
50
+ doc_dirs = [Rails.root.join('doc'), Rails.root.join('docs')]
51
+
52
+ doc_dirs.each do |doc_dir|
53
+ next unless Dir.exist?(doc_dir)
54
+
55
+ Dir.glob("#{doc_dir}/**/*.md").each do |file|
56
+ content = File.read(file)
57
+ title = File.basename(file, '.md').titleize
58
+
59
+ add_knowledge_entry(
60
+ title: title,
61
+ content: extract_summary(content),
62
+ source_type: "documentation",
63
+ source_url: file.sub(Rails.root.to_s, '')
64
+ )
65
+ end
66
+ end
67
+ end
68
+
69
+ def index_route_files
70
+ routes_file = Rails.root.join('config', 'routes.rb')
71
+ return unless File.exist?(routes_file)
72
+
73
+ content = File.read(routes_file)
74
+ routes_info = extract_routes_info(content)
75
+
76
+ add_knowledge_entry(
77
+ title: "Available Routes and Features",
78
+ content: routes_info,
79
+ source_type: "routes",
80
+ source_url: "/routes"
81
+ )
82
+ end
83
+
84
+ def index_model_files
85
+ models_dir = Rails.root.join('app', 'models')
86
+ return unless Dir.exist?(models_dir)
87
+
88
+ model_info = []
89
+ Dir.glob("#{models_dir}/**/*.rb").each do |file|
90
+ next if File.basename(file).start_with?('application_', 'concerns/')
91
+
92
+ content = File.read(file)
93
+ model_name = extract_model_name(content, file)
94
+ next unless model_name
95
+
96
+ description = extract_model_description(content)
97
+ model_info << "#{model_name}: #{description}" if description.present?
98
+ end
99
+
100
+ if model_info.any?
101
+ add_knowledge_entry(
102
+ title: "Application Models",
103
+ content: model_info.join("\n"),
104
+ source_type: "models",
105
+ source_url: "/models"
106
+ )
107
+ end
108
+ end
109
+
110
+ def index_custom_content
111
+ # Add default service information
112
+ add_knowledge_entry(
113
+ title: "Services",
114
+ content: RailsChatbot.configuration.default_responses[:services],
115
+ source_type: "default",
116
+ source_url: "/services"
117
+ )
118
+ end
119
+
120
+ def extract_summary(content)
121
+ # Extract first paragraph or main points
122
+ lines = content.split("\n")
123
+ summary_lines = []
124
+
125
+ lines.each do |line|
126
+ line = line.strip
127
+ next if line.empty? || line.start_with?('#', '*', '-', '=')
128
+
129
+ summary_lines << line
130
+ break if summary_lines.length >= 3
131
+ end
132
+
133
+ summary_lines.join(' ').truncate(500)
134
+ end
135
+
136
+ def extract_routes_info(content)
137
+ # Extract route information from routes.rb
138
+ routes = []
139
+
140
+ # Look for common route patterns
141
+ content.scan(/resources\s+:(\w+)/) do |match|
142
+ routes << "#{match[0].pluralize} management"
143
+ end
144
+
145
+ content.scan(/get\s+['"]([^'"]+)['"]/) do |match|
146
+ routes << match[0]
147
+ end
148
+
149
+ if routes.any?
150
+ "Available features: #{routes.join(', ')}"
151
+ else
152
+ "Web application with various features and endpoints"
153
+ end
154
+ end
155
+
156
+ def extract_model_name(content, file)
157
+ match = content.match(/class\s+(\w+)/)
158
+ match ? match[1] : File.basename(file, '.rb').camelize
159
+ end
160
+
161
+ def extract_model_description(content)
162
+ # Look for comments at the beginning of the class
163
+ lines = content.split("\n")
164
+ description_lines = []
165
+
166
+ in_comment = false
167
+ lines.each do |line|
168
+ line = line.strip
169
+
170
+ if line.start_with?('class')
171
+ break
172
+ elsif line.start_with?('#')
173
+ description_lines << line.sub(/^#\s*/, '')
174
+ elsif !line.empty? && description_lines.any?
175
+ break
176
+ end
177
+ end
178
+
179
+ description_lines.join(' ').truncate(200)
180
+ end
181
+
182
+ def add_knowledge_entry(title:, content:, source_type:, source_url: nil)
183
+ # Check if entry already exists
184
+ existing = KnowledgeBase.find_by(
185
+ title: title,
186
+ source_type: source_type,
187
+ source_url: source_url
188
+ )
189
+
190
+ if existing
191
+ existing.update!(content: content, updated_at: Time.current)
192
+ else
193
+ KnowledgeBase.create!(
194
+ title: title,
195
+ content: content,
196
+ source_type: source_type,
197
+ source_url: source_url
198
+ )
199
+ end
200
+ rescue => e
201
+ Rails.logger.error "Error adding knowledge entry: #{e.message}"
202
+ end
203
+
204
+ def find_default_response(query)
205
+ query_lower = query.downcase
206
+
207
+ case query_lower
208
+ when /services?, what.*service?, what.*do.*do?/
209
+ {
210
+ title: "Services Information",
211
+ content: RailsChatbot.configuration.default_responses[:services],
212
+ source_type: "default",
213
+ source_url: "/services"
214
+ }
215
+ when /hello?, hi?, hey?/
216
+ {
217
+ title: "Greeting",
218
+ content: RailsChatbot.configuration.default_responses[:greeting],
219
+ source_type: "default",
220
+ source_url: "/greeting"
221
+ }
222
+ else
223
+ {
224
+ title: "General Response",
225
+ content: RailsChatbot.configuration.default_responses[:no_results],
226
+ source_type: "default",
227
+ source_url: "/general"
228
+ }
229
+ end
230
+ end
231
+ end
232
+ end
233
+ end
@@ -8,34 +8,54 @@ module RailsChatbot
8
8
  end
9
9
 
10
10
  def process_message(user_message)
11
- # Retrieve relevant knowledge
12
- knowledge_service = KnowledgeRetrievalService.new(query: user_message)
13
- context = knowledge_service.format_context
14
-
11
+ # Retrieve relevant knowledge from application content
12
+ knowledge_results = ApplicationContentService.search_application_content(user_message)
13
+
15
14
  # Get conversation history
16
15
  messages = conversation.conversation_history
17
16
 
18
17
  # Add the new user message
19
18
  conversation.add_user_message(user_message)
20
19
 
20
+ # Format context from knowledge results
21
+ context = format_knowledge_context(knowledge_results)
22
+
21
23
  # Generate response
22
- response = llm_service.chat(
23
- messages: messages,
24
- context: context
25
- )
24
+ response = if knowledge_results.any? && knowledge_results.first[:source_type] == 'default'
25
+ # Use default response directly
26
+ knowledge_results.first[:content]
27
+ else
28
+ # Generate response using LLM with context
29
+ llm_service.chat(
30
+ messages: messages,
31
+ context: context
32
+ )
33
+ end
26
34
 
27
35
  # Save assistant response
28
36
  conversation.add_assistant_message(
29
37
  response,
30
38
  metadata: {
31
- knowledge_results: knowledge_service.retrieve.map { |r| r.slice(:title, :source_type, :source_id) }
39
+ knowledge_results: knowledge_results.map { |r| r.slice(:title, :source_type, :source_id) }
32
40
  }
33
41
  )
34
42
 
35
43
  {
36
44
  response: response,
37
- knowledge_sources: knowledge_service.retrieve
45
+ knowledge_sources: knowledge_results
38
46
  }
39
47
  end
48
+
49
+ private
50
+
51
+ def format_knowledge_context(knowledge_results)
52
+ return "" if knowledge_results.empty?
53
+
54
+ context_parts = knowledge_results.map do |result|
55
+ "#{result[:title]}: #{result[:content]}"
56
+ end
57
+
58
+ "Relevant information:\n#{context_parts.join("\n\n")}"
59
+ end
40
60
  end
41
61
  end
@@ -13,5 +13,9 @@
13
13
 
14
14
  <%= yield %>
15
15
 
16
+ <% if chatbot_widget_enabled? %>
17
+ <%= include_chatbot_widget %>
18
+ <% end %>
19
+
16
20
  </body>
17
21
  </html>
@@ -127,12 +127,12 @@
127
127
  </style>
128
128
  <% end %>
129
129
 
130
- <div class="chatbot-container" data-controller="chatbot">
130
+ <div class="chatbot-container">
131
131
  <div class="chatbot-header">
132
132
  <%= RailsChatbot.configuration.chatbot_title %>
133
133
  </div>
134
134
 
135
- <div class="chatbot-messages" data-chatbot-target="messages">
135
+ <div class="chatbot-messages" id="chatbot-messages">
136
136
  <div class="message assistant">
137
137
  <div class="message-bubble">
138
138
  Hello! I'm your application assistant. How can I help you today?
@@ -144,14 +144,12 @@
144
144
  <input
145
145
  type="text"
146
146
  class="chatbot-input"
147
+ id="chatbot-input"
147
148
  placeholder="Type your message..."
148
- data-chatbot-target="input"
149
- data-action="keydown->chatbot#handleKeyDown"
150
149
  >
151
150
  <button
152
151
  class="chatbot-send"
153
- data-action="click->chatbot#sendMessage"
154
- data-chatbot-target="sendButton"
152
+ id="chatbot-send"
155
153
  >
156
154
  Send
157
155
  </button>
@@ -160,6 +158,6 @@
160
158
 
161
159
  <script>
162
160
  window.RailsChatbot = window.RailsChatbot || {};
163
- window.RailsChatbot.routes = <%= rails_chatbot_routes %>;
161
+ window.RailsChatbot.routes = <%= @rails_chatbot_routes %>;
164
162
  </script>
165
163
  <%= javascript_include_tag "rails_chatbot/application" %>
@@ -0,0 +1,56 @@
1
+ <!-- Floating Chatbot Widget -->
2
+ <div class="chatbot-widget">
3
+ <!-- Chat Icon -->
4
+ <div class="chatbot-icon" id="chatbot-toggle">
5
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
6
+ <path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v10z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
7
+ <path d="M8 10h.01M12 10h.01M16 10h.01" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
8
+ </svg>
9
+ <span class="chatbot-badge" id="unread-badge" style="display: none;">1</span>
10
+ </div>
11
+
12
+ <!-- Chat Window -->
13
+ <div class="chatbot-window" id="chatbot-window">
14
+ <div class="chatbot-header">
15
+ <div class="chatbot-title">
16
+ <%= RailsChatbot.configuration.chatbot_title %>
17
+ </div>
18
+ <button class="chatbot-close" id="chatbot-close">
19
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
20
+ <path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
21
+ </svg>
22
+ </button>
23
+ </div>
24
+
25
+ <div class="chatbot-messages" id="chatbot-messages">
26
+ <div class="message assistant">
27
+ <div class="message-bubble">
28
+ Hello! I'm your application assistant. How can I help you today?
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div class="chatbot-input-container">
34
+ <input
35
+ type="text"
36
+ class="chatbot-input"
37
+ id="chatbot-input"
38
+ placeholder="Type your message..."
39
+ >
40
+ <button
41
+ class="chatbot-send"
42
+ id="chatbot-send"
43
+ >
44
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
45
+ <path d="M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
46
+ </svg>
47
+ </button>
48
+ </div>
49
+ </div>
50
+ </div>
51
+
52
+ <script>
53
+ window.RailsChatbot = window.RailsChatbot || {};
54
+ window.RailsChatbot.routes = <%= rails_chatbot_routes %>;
55
+ </script>
56
+ <%= javascript_include_tag "rails_chatbot/widget" %>
@@ -1,3 +1,3 @@
1
1
  module RailsChatbot
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/rails_chatbot.rb CHANGED
@@ -15,7 +15,7 @@ module RailsChatbot
15
15
  end
16
16
 
17
17
  class Configuration
18
- attr_accessor :openai_api_key, :openai_model, :chatbot_title, :current_user_proc, :enable_knowledge_base_indexing, :indexable_models
18
+ attr_accessor :openai_api_key, :openai_model, :chatbot_title, :current_user_proc, :enable_knowledge_base_indexing, :indexable_models, :enable_widget, :default_responses
19
19
 
20
20
  def initialize
21
21
  @openai_api_key = ENV['OPENAI_API_KEY']
@@ -24,6 +24,13 @@ module RailsChatbot
24
24
  @enable_knowledge_base_indexing = true
25
25
  @current_user_proc = nil
26
26
  @indexable_models = nil # Array of model classes to index
27
+ @enable_widget = true # Enable floating widget
28
+ @default_responses = {
29
+ greeting: "Hello! I'm your application assistant. How can I help you today?",
30
+ services: "We are working on web-based applications using Ruby on Rails.",
31
+ no_results: "I don't have specific information about that, but I'm here to help with general questions about our services.",
32
+ error: "Sorry, I encountered an error. Please try again or contact support."
33
+ }
27
34
  end
28
35
  end
29
36
  end
@@ -25,6 +25,35 @@ namespace :rails_chatbot do
25
25
  puts "\nDone!"
26
26
  end
27
27
 
28
+ desc "Index application content for knowledge base"
29
+ task index_application_content: :environment do
30
+ puts "Indexing application content..."
31
+ RailsChatbot::ApplicationContentService.index_application_content
32
+ puts "Application content indexed successfully!"
33
+ end
34
+
35
+ desc "Add default knowledge entries"
36
+ task add_default_knowledge: :environment do
37
+ puts "Adding default knowledge entries..."
38
+
39
+ RailsChatbot::KnowledgeIndexer.add_custom_knowledge(
40
+ title: "Services",
41
+ content: RailsChatbot.configuration.default_responses[:services],
42
+ source_type: "default",
43
+ source_url: "/services"
44
+ )
45
+
46
+ puts "Default knowledge entries added!"
47
+ end
48
+
49
+ desc "Setup chatbot (index content + add defaults)"
50
+ task setup: :environment do
51
+ puts "Setting up RailsChatbot..."
52
+ Rake::Task['rails_chatbot:index_application_content'].invoke
53
+ Rake::Task['rails_chatbot:add_default_knowledge'].invoke
54
+ puts "RailsChatbot setup complete!"
55
+ end
56
+
28
57
  desc "Clear knowledge base"
29
58
  task clear_knowledge_base: :environment do
30
59
  count = RailsChatbot::KnowledgeBase.count
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_chatbot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Burraq Ur Rehman
@@ -63,6 +63,7 @@ files:
63
63
  - MIT-LICENSE
64
64
  - README.md
65
65
  - Rakefile
66
+ - app/assets/config/rails_chatbot_manifest.js
66
67
  - app/assets/stylesheets/rails_chatbot/application.css
67
68
  - app/controllers/rails_chatbot/application_controller.rb
68
69
  - app/controllers/rails_chatbot/chat_controller.rb
@@ -70,17 +71,20 @@ files:
70
71
  - app/controllers/rails_chatbot/messages_controller.rb
71
72
  - app/helpers/rails_chatbot/application_helper.rb
72
73
  - app/javascript/rails_chatbot/application.js
74
+ - app/javascript/rails_chatbot/widget.js
73
75
  - app/jobs/rails_chatbot/application_job.rb
74
76
  - app/mailers/rails_chatbot/application_mailer.rb
75
77
  - app/models/rails_chatbot/application_record.rb
76
78
  - app/models/rails_chatbot/conversation.rb
77
79
  - app/models/rails_chatbot/knowledge_base.rb
78
80
  - app/models/rails_chatbot/message.rb
81
+ - app/services/rails_chatbot/application_content_service.rb
79
82
  - app/services/rails_chatbot/chat_service.rb
80
83
  - app/services/rails_chatbot/knowledge_retrieval_service.rb
81
84
  - app/services/rails_chatbot/llm_service.rb
82
85
  - app/views/layouts/rails_chatbot/application.html.erb
83
86
  - app/views/rails_chatbot/chat/index.html.erb
87
+ - app/views/rails_chatbot/shared/_chat_widget.html.erb
84
88
  - config/initializers/rails_chatbot.rb
85
89
  - config/routes.rb
86
90
  - db/migrate/001_create_rails_chatbot_conversations.rb