polylingo_chat 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/README.md +432 -0
- data/lib/generators/polylingo_chat/install/install_generator.rb +107 -0
- data/lib/generators/polylingo_chat/install/templates/README +48 -0
- data/lib/generators/polylingo_chat/install/templates/channels/application_cable/channel.rb +4 -0
- data/lib/generators/polylingo_chat/install/templates/channels/application_cable/connection.rb +30 -0
- data/lib/generators/polylingo_chat/install/templates/channels/polylingo_chat_channel.rb +15 -0
- data/lib/generators/polylingo_chat/install/templates/create_conversations.rb +9 -0
- data/lib/generators/polylingo_chat/install/templates/create_messages.rb +13 -0
- data/lib/generators/polylingo_chat/install/templates/create_participants.rb +12 -0
- data/lib/generators/polylingo_chat/install/templates/initializer.rb +24 -0
- data/lib/generators/polylingo_chat/install/templates/javascript/channels/consumer.js +15 -0
- data/lib/generators/polylingo_chat/install/templates/javascript/channels/index.js +2 -0
- data/lib/generators/polylingo_chat/install/templates/javascript/chat.js +86 -0
- data/lib/generators/polylingo_chat/install/templates/models/conversation.rb +7 -0
- data/lib/generators/polylingo_chat/install/templates/models/message.rb +14 -0
- data/lib/generators/polylingo_chat/install/templates/models/participant.rb +6 -0
- data/lib/generators/polylingo_chat/install_generator.rb +38 -0
- data/lib/generators/polylingo_chat/templates/INSTALL_README.md +124 -0
- data/lib/generators/polylingo_chat/templates/chat_channel_example.js +18 -0
- data/lib/generators/polylingo_chat/templates/create_polyglot_conversations.rb +9 -0
- data/lib/generators/polylingo_chat/templates/create_polyglot_messages.rb +13 -0
- data/lib/generators/polylingo_chat/templates/create_polyglot_participants.rb +12 -0
- data/lib/generators/polylingo_chat/templates/models/conversation.rb +7 -0
- data/lib/generators/polylingo_chat/templates/models/message.rb +14 -0
- data/lib/generators/polylingo_chat/templates/models/participant.rb +6 -0
- data/lib/generators/polylingo_chat/templates/polyglot.rb +53 -0
- data/lib/generators/polylingo_chat/templates/polylingo_chat_channel.rb +19 -0
- data/lib/polylingo_chat/config.rb +26 -0
- data/lib/polylingo_chat/engine.rb +10 -0
- data/lib/polylingo_chat/railtie.rb +14 -0
- data/lib/polylingo_chat/realtime.rb +8 -0
- data/lib/polylingo_chat/translate_job.rb +63 -0
- data/lib/polylingo_chat/translator/anthropic_client.rb +85 -0
- data/lib/polylingo_chat/translator/base.rb +13 -0
- data/lib/polylingo_chat/translator/gemini_client.rb +88 -0
- data/lib/polylingo_chat/translator/openai_client.rb +92 -0
- data/lib/polylingo_chat/translator.rb +40 -0
- data/lib/polylingo_chat/version.rb +3 -0
- data/lib/polylingo_chat.rb +11 -0
- metadata +138 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 03e17e594f585909ba895a3dab27bda4b0f731bd59fd12fac90dd5a0df7afe2e
|
|
4
|
+
data.tar.gz: e0ae5358db6d20f888e1524af38458e3457a9583bd31837a3ca6db3d33937d68
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 868e647b05dd1d14836fcbe8aa794cdb6f5ffd0dd3629255e79833e849a963bf6c7ad2b58d0bfd146aec673b2305ab2706dcb7d656bf1a53dfeb805613258478
|
|
7
|
+
data.tar.gz: 26d96b4e747de39519e76f4c3158215caebd940e5acbbd656f6403039904df4f7d63cfe9d627e750b884f7e9c27053be3c6b30b1c047dbc96891351095093189
|
data/README.md
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
# PolylingoChat โ Real-time Multilingual Chat Engine for Rails
|
|
2
|
+
|
|
3
|
+
PolylingoChat is a plug-and-play Rails Engine that adds real-time chat to your Rails app. Use it as a **chat-only engine** or enable **AI-powered translation** for multilingual conversations.
|
|
4
|
+
|
|
5
|
+
Perfect for marketplaces, SaaS apps, CRMs, support systems, or global communities.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## โจ Features
|
|
10
|
+
|
|
11
|
+
- ๐ฅ **Real-time chat** using ActionCable with Solid Cable (database-backed)
|
|
12
|
+
- ๐ **One-command installer** โ everything set up automatically
|
|
13
|
+
- ๐ **Optional AI translation** - works with or without translation
|
|
14
|
+
- โ๏ธ **Multi-provider support** โ OpenAI, Anthropic Claude, or Google Gemini
|
|
15
|
+
- ๐งฉ **Rails Engine** โ mounts directly inside your app
|
|
16
|
+
- ๐ฅ **Conversations, Participants & Messages** models included
|
|
17
|
+
- ๐ **Works with any ActiveJob backend** (Sidekiq, Solid Queue, Delayed Job, etc.)
|
|
18
|
+
- ๐ **Secure & scoped** ActionCable channels
|
|
19
|
+
- ๐งฑ **Extendable architecture** (custom UI, custom providers, custom storage)
|
|
20
|
+
- ๐พ **Built-in caching** support for translations
|
|
21
|
+
- ๐งช **RSpec testing** framework included
|
|
22
|
+
- ๐๏ธ **No Redis required** โ uses Solid Cable for WebSocket persistence
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ๐ Requirements
|
|
27
|
+
|
|
28
|
+
- Ruby >= 2.7.0
|
|
29
|
+
- Rails >= 6.0
|
|
30
|
+
- A background job processor (Sidekiq, Solid Queue, or Delayed Job)
|
|
31
|
+
- **Optional:** AI API key (only if you want translation)
|
|
32
|
+
|
|
33
|
+
**Note:** PolylingoChat uses **Solid Cable** (database-backed ActionCable) by default, so Redis is optional.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## ๐ Installation
|
|
38
|
+
|
|
39
|
+
### 1. Add the gem
|
|
40
|
+
```ruby
|
|
41
|
+
# Gemfile
|
|
42
|
+
gem "polylingo_chat", github: "AdwareTechnologies/polylingo_chat"
|
|
43
|
+
|
|
44
|
+
# Add a background job processor
|
|
45
|
+
gem 'sidekiq' # or 'solid_queue' or 'delayed_job_active_record'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 2. Install and generate
|
|
49
|
+
```bash
|
|
50
|
+
bundle install
|
|
51
|
+
bin/rails generate polylingo_chat:install
|
|
52
|
+
bin/rails db:migrate
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**The installer automatically:**
|
|
56
|
+
- โ
Creates migrations for Conversations, Participants, and Messages
|
|
57
|
+
- โ
Generates model files with associations
|
|
58
|
+
- โ
Sets up ActionCable channels (PolylinguoChatChannel)
|
|
59
|
+
- โ
Creates JavaScript files for real-time chat
|
|
60
|
+
- โ
Configures Solid Cable in `config/cable.yml`
|
|
61
|
+
- โ
Downloads ActionCable ESM module to `vendor/javascript`
|
|
62
|
+
- โ
Updates `config/routes.rb` with chat routes
|
|
63
|
+
- โ
Updates `config/importmap.rb` with required pins
|
|
64
|
+
- โ
Updates `app/javascript/application.js` with chat import
|
|
65
|
+
- โ
Creates `config/initializers/polylingo_chat.rb` for configuration
|
|
66
|
+
|
|
67
|
+
### 3. Configure ActiveJob
|
|
68
|
+
```ruby
|
|
69
|
+
# config/application.rb or config/environments/production.rb
|
|
70
|
+
config.active_job.queue_adapter = :sidekiq # or :solid_queue, :delayed_job, :async
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 4. Add preferred_language to User (optional, only if using translation)
|
|
74
|
+
```bash
|
|
75
|
+
bin/rails generate migration AddPreferredLanguageToUsers preferred_language:string
|
|
76
|
+
bin/rails db:migrate
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 5. Configure PolylingoChat
|
|
80
|
+
|
|
81
|
+
Edit `config/initializers/polylingo_chat.rb` (created by installer):
|
|
82
|
+
|
|
83
|
+
**Option A: Chat-Only Mode (No Translation)**
|
|
84
|
+
```ruby
|
|
85
|
+
PolylingoChat.configure do |config|
|
|
86
|
+
# Leave api_key as nil for chat-only mode
|
|
87
|
+
config.api_key = nil
|
|
88
|
+
|
|
89
|
+
config.queue_adapter = :sidekiq
|
|
90
|
+
config.async = true
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Option B: With AI Translation**
|
|
95
|
+
```ruby
|
|
96
|
+
PolylingoChat.configure do |config|
|
|
97
|
+
# Enable translation by setting API key
|
|
98
|
+
config.provider = :openai # or :anthropic, :gemini
|
|
99
|
+
config.api_key = ENV['OPENAI_API_KEY']
|
|
100
|
+
config.model = 'gpt-4o-mini'
|
|
101
|
+
|
|
102
|
+
config.queue_adapter = :sidekiq
|
|
103
|
+
config.default_language = 'en'
|
|
104
|
+
config.cache_store = Rails.cache
|
|
105
|
+
config.timeout = 15
|
|
106
|
+
config.async = true
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 6. Configure ActionCable Authentication
|
|
111
|
+
|
|
112
|
+
The installer creates `app/channels/application_cable/connection.rb` with authentication logic. For development, it accepts `user_id` from WebSocket params:
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# app/channels/application_cable/connection.rb
|
|
116
|
+
def find_verified_user
|
|
117
|
+
user_id = request.params[:user_id] || cookies.encrypted[:user_id]
|
|
118
|
+
|
|
119
|
+
if user_id && (verified_user = User.find_by(id: user_id))
|
|
120
|
+
verified_user
|
|
121
|
+
else
|
|
122
|
+
reject_unauthorized_connection
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
The JavaScript consumer automatically passes `window.currentUserId` to authenticate. Make sure to set this in your views:
|
|
128
|
+
|
|
129
|
+
```erb
|
|
130
|
+
<!-- app/views/conversations/show.html.erb -->
|
|
131
|
+
<script>
|
|
132
|
+
window.conversationId = <%= @conversation.id %>;
|
|
133
|
+
window.currentUserId = <%= current_user.id %>;
|
|
134
|
+
</script>
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**For production:** Replace the `find_verified_user` method with your actual authentication (Devise, session, JWT, etc.)
|
|
138
|
+
|
|
139
|
+
### 7. Start your background worker
|
|
140
|
+
```bash
|
|
141
|
+
bundle exec sidekiq # or bin/rails solid_queue:start, or bin/rails jobs:work
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## ๐ฏ Usage
|
|
147
|
+
|
|
148
|
+
### 1. Set up your view
|
|
149
|
+
|
|
150
|
+
First, expose the conversation and user IDs to JavaScript in your chat view:
|
|
151
|
+
|
|
152
|
+
```erb
|
|
153
|
+
<!-- app/views/conversations/show.html.erb -->
|
|
154
|
+
<script>
|
|
155
|
+
window.conversationId = <%= @conversation.id %>;
|
|
156
|
+
window.currentUserId = <%= current_user.id %>;
|
|
157
|
+
</script>
|
|
158
|
+
|
|
159
|
+
<div id="messages">
|
|
160
|
+
<!-- Messages will appear here via ActionCable -->
|
|
161
|
+
</div>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### 2. Send a message
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
# Create a conversation
|
|
168
|
+
conversation = Conversation.create!(title: "Project Discussion")
|
|
169
|
+
|
|
170
|
+
# Add participants
|
|
171
|
+
Participant.create!(conversation: conversation, user: user1)
|
|
172
|
+
Participant.create!(conversation: conversation, user: user2)
|
|
173
|
+
|
|
174
|
+
# Send a message
|
|
175
|
+
Message.create!(
|
|
176
|
+
conversation: conversation,
|
|
177
|
+
sender: user1,
|
|
178
|
+
body: "Hello! How are you?"
|
|
179
|
+
)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**What happens:**
|
|
183
|
+
- Message is saved to database
|
|
184
|
+
- Background job is enqueued automatically
|
|
185
|
+
- **Without API key:** Message is broadcast as-is to all participants
|
|
186
|
+
- **With API key:** Message is translated to each participant's preferred language
|
|
187
|
+
- All participants see the message in real-time via ActionCable
|
|
188
|
+
|
|
189
|
+
### 3. Real-time updates with ActionCable
|
|
190
|
+
|
|
191
|
+
The installer creates JavaScript files that handle real-time updates automatically. The key file is `app/javascript/chat.js`:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
// This file is automatically generated by the installer
|
|
195
|
+
// It connects to PolylinguoChatChannel and handles incoming messages
|
|
196
|
+
|
|
197
|
+
import consumer from "channels/consumer"
|
|
198
|
+
|
|
199
|
+
consumer.subscriptions.create({
|
|
200
|
+
channel: "PolylinguoChatChannel",
|
|
201
|
+
conversation_id: window.conversationId
|
|
202
|
+
}, {
|
|
203
|
+
received(data) {
|
|
204
|
+
// data.message - message text (translated if translation enabled)
|
|
205
|
+
// data.original - original text
|
|
206
|
+
// data.sender_id - sender's ID
|
|
207
|
+
// data.translated - boolean (true if translation was used)
|
|
208
|
+
console.log("Received:", data.message)
|
|
209
|
+
console.log("Was translated:", data.translated)
|
|
210
|
+
|
|
211
|
+
// The generated code automatically appends messages to #messages div
|
|
212
|
+
}
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**You don't need to write this code** โ the installer creates it for you!
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## ๐ Translation (Optional)
|
|
221
|
+
|
|
222
|
+
### When to Use Translation
|
|
223
|
+
|
|
224
|
+
Translation is **optional**. Use it when:
|
|
225
|
+
- โ
You have a global user base speaking different languages
|
|
226
|
+
- โ
You want automatic message translation
|
|
227
|
+
- โ
You're willing to pay for AI API usage
|
|
228
|
+
|
|
229
|
+
Skip translation when:
|
|
230
|
+
- โ All users speak the same language
|
|
231
|
+
- โ You want a simple chat without AI costs
|
|
232
|
+
- โ You'll handle translation elsewhere
|
|
233
|
+
|
|
234
|
+
### AI Providers
|
|
235
|
+
|
|
236
|
+
#### OpenAI
|
|
237
|
+
```ruby
|
|
238
|
+
config.provider = :openai
|
|
239
|
+
config.api_key = ENV['OPENAI_API_KEY']
|
|
240
|
+
config.model = 'gpt-4o-mini' # Fast & cost-effective
|
|
241
|
+
```
|
|
242
|
+
Get key: https://platform.openai.com/api-keys
|
|
243
|
+
|
|
244
|
+
**Models:** `gpt-4o-mini`, `gpt-4o`, `gpt-3.5-turbo`
|
|
245
|
+
|
|
246
|
+
#### Anthropic Claude
|
|
247
|
+
```ruby
|
|
248
|
+
config.provider = :anthropic
|
|
249
|
+
config.api_key = ENV['ANTHROPIC_API_KEY']
|
|
250
|
+
config.model = 'claude-3-5-sonnet-20241022'
|
|
251
|
+
```
|
|
252
|
+
Get key: https://console.anthropic.com/
|
|
253
|
+
|
|
254
|
+
**Models:** `claude-3-5-sonnet-20241022`, `claude-3-5-haiku-20241022`, `claude-3-opus-20240229`
|
|
255
|
+
|
|
256
|
+
#### Google Gemini
|
|
257
|
+
```ruby
|
|
258
|
+
config.provider = :gemini
|
|
259
|
+
config.api_key = ENV['GOOGLE_API_KEY']
|
|
260
|
+
config.model = 'gemini-1.5-flash'
|
|
261
|
+
```
|
|
262
|
+
Get key: https://ai.google.dev/
|
|
263
|
+
|
|
264
|
+
**Models:** `gemini-1.5-flash`, `gemini-1.5-pro`
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## โ๏ธ Configuration Options
|
|
269
|
+
|
|
270
|
+
| Option | Type | Default | Description |
|
|
271
|
+
|--------|------|---------|-------------|
|
|
272
|
+
| `provider` | Symbol | `:openai` | AI provider (`:openai`, `:anthropic`, `:gemini`) |
|
|
273
|
+
| `api_key` | String | `nil` | API key - leave nil for chat-only mode |
|
|
274
|
+
| `model` | String | `'gpt-4o-mini'` | Model name for the provider |
|
|
275
|
+
| `queue_adapter` | Symbol | `nil` | Which ActiveJob adapter you're using (informational) |
|
|
276
|
+
| `default_language` | String | `'en'` | Default target language (ISO 639-1 code) |
|
|
277
|
+
| `cache_store` | Cache | `nil` | Rails cache store for caching translations |
|
|
278
|
+
| `async` | Boolean | `true` | Enable async processing via ActiveJob |
|
|
279
|
+
| `timeout` | Integer | `15` | API request timeout in seconds |
|
|
280
|
+
|
|
281
|
+
---
|
|
282
|
+
|
|
283
|
+
## ๐๏ธ How It Works
|
|
284
|
+
|
|
285
|
+
### Message Flow
|
|
286
|
+
|
|
287
|
+
1. User sends a message โ saved to database
|
|
288
|
+
2. `Message#after_create_commit` enqueues job
|
|
289
|
+
3. Job runs in background via your ActiveJob adapter
|
|
290
|
+
4. **If API key present:** Message translated for each participant
|
|
291
|
+
5. **If no API key:** Original message used for all participants
|
|
292
|
+
6. Messages broadcast via ActionCable (using Solid Cable)
|
|
293
|
+
7. Recipients see messages in real-time
|
|
294
|
+
|
|
295
|
+
### Models
|
|
296
|
+
|
|
297
|
+
**Conversation** - Chat conversation with many participants and messages
|
|
298
|
+
|
|
299
|
+
**Participant** - Join table linking Users to Conversations
|
|
300
|
+
|
|
301
|
+
**Message** - Individual chat message with translation tracking
|
|
302
|
+
|
|
303
|
+
### Solid Cable (Database-Backed WebSockets)
|
|
304
|
+
|
|
305
|
+
PolylingoChat uses **Solid Cable** instead of Redis for ActionCable. Benefits:
|
|
306
|
+
|
|
307
|
+
- โ
**No Redis dependency** - One less service to manage
|
|
308
|
+
- โ
**Message persistence** - WebSocket messages stored in database
|
|
309
|
+
- โ
**Simpler deployment** - Works anywhere your database works
|
|
310
|
+
- โ
**Better for development** - No need to run Redis locally
|
|
311
|
+
- โ
**Cost-effective** - No separate Redis hosting required
|
|
312
|
+
|
|
313
|
+
Solid Cable uses your existing database (SQLite, PostgreSQL, MySQL) to store WebSocket messages with configurable retention:
|
|
314
|
+
|
|
315
|
+
```yaml
|
|
316
|
+
# config/cable.yml (configured automatically by installer)
|
|
317
|
+
development:
|
|
318
|
+
adapter: solid_cable
|
|
319
|
+
polling_interval: 0.1.seconds
|
|
320
|
+
message_retention: 1.day
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
The installer runs `bin/rails solid_cable:install` automatically, which creates the necessary migrations.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## ๐งช Running Tests
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
bundle exec rspec
|
|
331
|
+
|
|
332
|
+
# Current status:
|
|
333
|
+
# 29 examples, 0 failures
|
|
334
|
+
# Line Coverage: 91.02%
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## ๐ Performance Tips
|
|
340
|
+
|
|
341
|
+
### 1. Enable Caching (if using translation)
|
|
342
|
+
```ruby
|
|
343
|
+
config.cache_store = Rails.cache
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### 2. Choose the Right Model
|
|
347
|
+
- **High-volume:** `gpt-4o-mini`, `claude-3-5-haiku`, `gemini-1.5-flash`
|
|
348
|
+
- **Quality:** `gpt-4o`, `claude-3-5-sonnet`, `gemini-1.5-pro`
|
|
349
|
+
|
|
350
|
+
### 3. Scale Your Job Processor
|
|
351
|
+
|
|
352
|
+
**Sidekiq:**
|
|
353
|
+
```yaml
|
|
354
|
+
# config/sidekiq.yml
|
|
355
|
+
:concurrency: 25
|
|
356
|
+
:queues:
|
|
357
|
+
- [polylingo_chat_translations, 10]
|
|
358
|
+
- [default, 5]
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Solid Queue:**
|
|
362
|
+
```yaml
|
|
363
|
+
# config/solid_queue.yml
|
|
364
|
+
workers:
|
|
365
|
+
- queues: polylingo_chat_translations
|
|
366
|
+
threads: 5
|
|
367
|
+
processes: 3
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
371
|
+
|
|
372
|
+
## ๐ Security
|
|
373
|
+
|
|
374
|
+
1. **API Keys:** Use environment variables, never commit
|
|
375
|
+
2. **User Scoping:** Ensure users can only access their conversations
|
|
376
|
+
3. **ActionCable Auth:** Secure channels with authentication
|
|
377
|
+
4. **Rate Limiting:** Consider limiting message creation
|
|
378
|
+
5. **Content Filtering:** Add profanity/spam filters if needed
|
|
379
|
+
|
|
380
|
+
---
|
|
381
|
+
|
|
382
|
+
## ๐ก Use Cases
|
|
383
|
+
|
|
384
|
+
### Chat-Only Mode (No Translation)
|
|
385
|
+
Perfect for:
|
|
386
|
+
- Internal team chat
|
|
387
|
+
- Customer support (same language)
|
|
388
|
+
- Community forums
|
|
389
|
+
- Simple messaging features
|
|
390
|
+
|
|
391
|
+
### With Translation
|
|
392
|
+
Perfect for:
|
|
393
|
+
- Global marketplaces
|
|
394
|
+
- International teams
|
|
395
|
+
- Multi-language support systems
|
|
396
|
+
- Cross-border collaboration
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## ๐ค Contributing
|
|
401
|
+
|
|
402
|
+
1. Fork the repository
|
|
403
|
+
2. Create your feature branch
|
|
404
|
+
3. Write tests for your changes
|
|
405
|
+
4. Commit and push
|
|
406
|
+
5. Open a Pull Request
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## ๐ License
|
|
411
|
+
|
|
412
|
+
MIT License
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## ๐ค Author
|
|
417
|
+
|
|
418
|
+
**Shoaib Malik**
|
|
419
|
+
- Email: shoaib2109@gmail.com
|
|
420
|
+
- GitHub: [@shoaibmalik786](https://github.com/shoaibmalik786)
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## ๐ Support
|
|
425
|
+
|
|
426
|
+
- ๐ **Issues:** [GitHub Issues](https://github.com/shoaibmalik786/polylingo_chat/issues)
|
|
427
|
+
- ๐ **Documentation:** [GitHub Wiki](https://github.com/shoaibmalik786/polylingo_chat/wiki)
|
|
428
|
+
- ๐ฌ **Discussions:** [GitHub Discussions](https://github.com/shoaibmalik786/polylingo_chat/discussions)
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
**Made with โค๏ธ for the global Rails community**
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
require 'rails/generators'
|
|
2
|
+
require 'rails/generators/migration'
|
|
3
|
+
|
|
4
|
+
module PolylingoChat
|
|
5
|
+
module Generators
|
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
|
7
|
+
include Rails::Generators::Migration
|
|
8
|
+
|
|
9
|
+
source_root File.expand_path('templates', __dir__)
|
|
10
|
+
|
|
11
|
+
desc "Installs PolylingoChat with real-time multilingual chat"
|
|
12
|
+
|
|
13
|
+
def self.next_migration_number(dirname)
|
|
14
|
+
next_migration_number = current_migration_number(dirname) + 1
|
|
15
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def copy_migrations
|
|
19
|
+
migration_template "create_conversations.rb", "db/migrate/create_conversations.rb"
|
|
20
|
+
migration_template "create_participants.rb", "db/migrate/create_participants.rb"
|
|
21
|
+
migration_template "create_messages.rb", "db/migrate/create_messages.rb"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_models
|
|
25
|
+
template "models/conversation.rb", "app/models/conversation.rb"
|
|
26
|
+
template "models/participant.rb", "app/models/participant.rb"
|
|
27
|
+
template "models/message.rb", "app/models/message.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create_channels
|
|
31
|
+
empty_directory "app/channels/application_cable"
|
|
32
|
+
template "channels/application_cable/channel.rb", "app/channels/application_cable/channel.rb"
|
|
33
|
+
template "channels/application_cable/connection.rb", "app/channels/application_cable/connection.rb"
|
|
34
|
+
template "channels/polylingo_chat_chat_channel.rb", "app/channels/polylingo_chat_chat_channel.rb"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def create_javascript_files
|
|
38
|
+
empty_directory "app/javascript/channels"
|
|
39
|
+
template "javascript/chat.js", "app/javascript/chat.js"
|
|
40
|
+
template "javascript/channels/consumer.js", "app/javascript/channels/consumer.js"
|
|
41
|
+
template "javascript/channels/index.js", "app/javascript/channels/index.js"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def configure_cable
|
|
45
|
+
if File.exist?("config/cable.yml")
|
|
46
|
+
gsub_file "config/cable.yml", /^development:\s*\n\s+adapter:\s+\w+.*$/m do |match|
|
|
47
|
+
"development:\n adapter: solid_cable\n polling_interval: 0.1.seconds\n message_retention: 1.day"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def setup_solid_cable
|
|
53
|
+
say "Setting up Solid Cable...", :green
|
|
54
|
+
rails_command "solid_cable:install", abort_on_failure: false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def download_actioncable
|
|
58
|
+
empty_directory "vendor/javascript"
|
|
59
|
+
say "Downloading ActionCable ESM module...", :green
|
|
60
|
+
run "curl -o vendor/javascript/@rails--actioncable.js https://ga.jspm.io/npm:@rails/actioncable@7.1.3/app/assets/javascripts/actioncable.esm.js"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def add_routes
|
|
64
|
+
route 'mount ActionCable.server => "/cable"'
|
|
65
|
+
route <<~RUBY
|
|
66
|
+
resources :conversations do
|
|
67
|
+
resources :messages, only: [:create]
|
|
68
|
+
end
|
|
69
|
+
RUBY
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def update_importmap
|
|
73
|
+
if File.exist?("config/importmap.rb")
|
|
74
|
+
append_to_file "config/importmap.rb" do
|
|
75
|
+
<<~RUBY
|
|
76
|
+
|
|
77
|
+
# PolylingoChat real-time chat
|
|
78
|
+
pin "@rails/actioncable", to: "@rails--actioncable.js"
|
|
79
|
+
pin_all_from "app/javascript/channels", under: "channels"
|
|
80
|
+
pin "chat", to: "chat.js"
|
|
81
|
+
RUBY
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_application_js
|
|
87
|
+
if File.exist?("app/javascript/application.js")
|
|
88
|
+
append_to_file "app/javascript/application.js" do
|
|
89
|
+
<<~JS
|
|
90
|
+
|
|
91
|
+
// PolylingoChat real-time chat
|
|
92
|
+
import "chat"
|
|
93
|
+
JS
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def create_initializer
|
|
99
|
+
template "initializer.rb", "config/initializers/polylingo_chat.rb"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def show_readme
|
|
103
|
+
readme "README" if behavior == :invoke
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
PolylingoChat has been installed successfully!
|
|
4
|
+
|
|
5
|
+
Next steps:
|
|
6
|
+
|
|
7
|
+
1. Run migrations:
|
|
8
|
+
rails db:migrate
|
|
9
|
+
|
|
10
|
+
2. Ensure your User model has a `preferred_language` column (optional):
|
|
11
|
+
rails g migration AddPreferredLanguageToUsers preferred_language:string
|
|
12
|
+
rails db:migrate
|
|
13
|
+
|
|
14
|
+
3. Configure your AI provider in config/initializers/polylingo_chat.rb:
|
|
15
|
+
- Set config.api_key with your API key
|
|
16
|
+
- Choose your provider (:openai, :anthropic, or :gemini)
|
|
17
|
+
- Or leave api_key as nil for chat-only mode (no translation)
|
|
18
|
+
|
|
19
|
+
4. Set up Solid Cable for production:
|
|
20
|
+
- Already configured for development
|
|
21
|
+
- For production, ensure database is configured in config/cable.yml
|
|
22
|
+
|
|
23
|
+
5. Import chat.js in your application.js:
|
|
24
|
+
// Add to app/javascript/application.js
|
|
25
|
+
import "chat"
|
|
26
|
+
|
|
27
|
+
6. Add this to your User model if it doesn't exist:
|
|
28
|
+
# app/models/user.rb
|
|
29
|
+
class User < ApplicationRecord
|
|
30
|
+
has_many :participants, dependent: :destroy
|
|
31
|
+
has_many :conversations, through: :participants
|
|
32
|
+
has_many :messages, foreign_key: 'sender_id', dependent: :destroy
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
7. In your conversation view, add these window variables:
|
|
36
|
+
<script>
|
|
37
|
+
window.conversationId = <%= @conversation.id %>;
|
|
38
|
+
window.currentUserId = <%= current_user.id %>;
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
8. Add a messages container in your view:
|
|
42
|
+
<div id="messages">
|
|
43
|
+
<!-- Messages will appear here in real-time -->
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
For more information, visit: https://github.com/yourusername/polylingo_chat
|
|
47
|
+
|
|
48
|
+
===============================================================================
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ApplicationCable
|
|
2
|
+
class Connection < ActionCable::Connection::Base
|
|
3
|
+
identified_by :current_user
|
|
4
|
+
|
|
5
|
+
def connect
|
|
6
|
+
self.current_user = find_verified_user
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def find_verified_user
|
|
12
|
+
# TODO: Implement your authentication logic here
|
|
13
|
+
# Example with Devise:
|
|
14
|
+
# if verified_user = env['warden'].user
|
|
15
|
+
# verified_user
|
|
16
|
+
# else
|
|
17
|
+
# reject_unauthorized_connection
|
|
18
|
+
# end
|
|
19
|
+
|
|
20
|
+
# For development/testing, accept user_id from request params or cookies:
|
|
21
|
+
user_id = request.params[:user_id] || cookies.encrypted[:user_id]
|
|
22
|
+
|
|
23
|
+
if user_id && (verified_user = User.find_by(id: user_id))
|
|
24
|
+
verified_user
|
|
25
|
+
else
|
|
26
|
+
reject_unauthorized_connection
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class PolylinguoChatChannel < ApplicationCable::Channel
|
|
2
|
+
def subscribed
|
|
3
|
+
conversation_id = params[:conversation_id]
|
|
4
|
+
|
|
5
|
+
# Subscribe to conversation-level channel for demo
|
|
6
|
+
stream_from "conversation_#{conversation_id}"
|
|
7
|
+
|
|
8
|
+
# Also subscribe to user-specific channel for production use
|
|
9
|
+
stream_from "polylingo_chat_recipient_#{current_user.id}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def unsubscribed
|
|
13
|
+
# Cleanup when channel is unsubscribed
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class CreateMessages < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :messages do |t|
|
|
4
|
+
t.references :sender, null: false, foreign_key: { to_table: :users }
|
|
5
|
+
t.references :conversation, null: false, foreign_key: true
|
|
6
|
+
t.text :body
|
|
7
|
+
t.string :language
|
|
8
|
+
t.text :translated_body
|
|
9
|
+
t.boolean :translated, default: false
|
|
10
|
+
t.timestamps
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
class CreateParticipants < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
2
|
+
def change
|
|
3
|
+
create_table :participants do |t|
|
|
4
|
+
t.references :user, null: false, foreign_key: true
|
|
5
|
+
t.references :conversation, null: false, foreign_key: true
|
|
6
|
+
t.string :role
|
|
7
|
+
t.timestamps
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
add_index :participants, [:user_id, :conversation_id], unique: true
|
|
11
|
+
end
|
|
12
|
+
end
|