polylingo_chat 0.1.0 โ 0.4.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 +4 -4
- data/README.md +383 -22
- data/lib/generators/polylingo_chat/install/README +72 -0
- data/lib/generators/polylingo_chat/install/install_generator.rb +44 -16
- data/lib/generators/polylingo_chat/install/templates/channels/polylingo_chat_channel.rb +1 -1
- data/lib/generators/polylingo_chat/install/templates/controllers/polylingo_chat/conversations_controller.rb +127 -0
- data/lib/generators/polylingo_chat/install/templates/controllers/polylingo_chat/messages_controller.rb +113 -0
- data/lib/generators/polylingo_chat/install/templates/create_conversations.rb +2 -2
- data/lib/generators/polylingo_chat/install/templates/create_message_translations.rb +13 -0
- data/lib/generators/polylingo_chat/install/templates/create_messages.rb +8 -4
- data/lib/generators/polylingo_chat/install/templates/create_participants.rb +8 -5
- data/lib/generators/polylingo_chat/install/templates/javascript/chat.js +5 -5
- data/lib/generators/polylingo_chat/install/templates/jobs/translate_job.rb +93 -0
- data/lib/generators/polylingo_chat/install/templates/models/conversation.rb +34 -5
- data/lib/generators/polylingo_chat/install/templates/models/message.rb +16 -8
- data/lib/generators/polylingo_chat/install/templates/models/participant.rb +19 -4
- data/lib/generators/polylingo_chat/install/templates/views/polylingo_chat/conversations/index.html.erb +56 -0
- data/lib/generators/polylingo_chat/install/templates/views/polylingo_chat/conversations/show.html.erb +141 -0
- data/lib/polylingo_chat/translator/anthropic_client.rb +2 -2
- data/lib/polylingo_chat/translator/gemini_client.rb +2 -2
- data/lib/polylingo_chat/translator/openai_client.rb +2 -2
- data/lib/polylingo_chat/version.rb +1 -1
- data/lib/polylingo_chat.rb +0 -1
- metadata +17 -29
- data/lib/generators/polylingo_chat/install_generator.rb +0 -38
- data/lib/generators/polylingo_chat/templates/INSTALL_README.md +0 -124
- data/lib/generators/polylingo_chat/templates/chat_channel_example.js +0 -18
- data/lib/generators/polylingo_chat/templates/create_polyglot_conversations.rb +0 -9
- data/lib/generators/polylingo_chat/templates/create_polyglot_messages.rb +0 -13
- data/lib/generators/polylingo_chat/templates/create_polyglot_participants.rb +0 -12
- data/lib/generators/polylingo_chat/templates/models/conversation.rb +0 -7
- data/lib/generators/polylingo_chat/templates/models/message.rb +0 -14
- data/lib/generators/polylingo_chat/templates/models/participant.rb +0 -6
- data/lib/generators/polylingo_chat/templates/polyglot.rb +0 -53
- data/lib/generators/polylingo_chat/templates/polylingo_chat_channel.rb +0 -19
- data/lib/polylingo_chat/translate_job.rb +0 -63
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a92a9a90c761c728d2f4d0d7a04377b2e69844ead7d84fe62e1351fc474a6a1c
|
|
4
|
+
data.tar.gz: 06f32c81a9236ff7ce661114c7fc45b38f6d1b0218fdc4d07840145209eb5898
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 94f7b6825b482ec13cdb62eda79595e92ee09324b8b8e40021ac14042fb5b40534818ea1cac3dfa78ad0669bc759339912268aee95982f9eb213d8cb4e7011e5
|
|
7
|
+
data.tar.gz: fcb8eb929491e17e596623e9c71af423302d1c40a35345f59cc614fed63f03924907c8daaf6d88ab871885d67b8018b0818307a255f253d763e36ed5bd460bf6
|
data/README.md
CHANGED
|
@@ -13,11 +13,12 @@ Perfect for marketplaces, SaaS apps, CRMs, support systems, or global communitie
|
|
|
13
13
|
- ๐ **Optional AI translation** - works with or without translation
|
|
14
14
|
- โ๏ธ **Multi-provider support** โ OpenAI, Anthropic Claude, or Google Gemini
|
|
15
15
|
- ๐งฉ **Rails Engine** โ mounts directly inside your app
|
|
16
|
-
-
|
|
16
|
+
- ๐ฑ **API-only mode** โ works with Rails API applications (auto-detected)
|
|
17
|
+
- ๐ฅ **Polymorphic associations** โ supports multiple participant types (User, Vendor, Customer, etc.)
|
|
17
18
|
- ๐ **Works with any ActiveJob backend** (Sidekiq, Solid Queue, Delayed Job, etc.)
|
|
18
19
|
- ๐ **Secure & scoped** ActionCable channels
|
|
19
20
|
- ๐งฑ **Extendable architecture** (custom UI, custom providers, custom storage)
|
|
20
|
-
- ๐พ **
|
|
21
|
+
- ๐พ **Database-backed translation caching** - translations stored and reused (zero API costs on retrieval)
|
|
21
22
|
- ๐งช **RSpec testing** framework included
|
|
22
23
|
- ๐๏ธ **No Redis required** โ uses Solid Cable for WebSocket persistence
|
|
23
24
|
|
|
@@ -53,17 +54,24 @@ bin/rails db:migrate
|
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
**The installer automatically:**
|
|
56
|
-
- โ
Creates migrations for
|
|
57
|
-
- โ
Generates
|
|
58
|
-
- โ
|
|
59
|
-
- โ
|
|
60
|
-
- โ
|
|
61
|
-
- โ
|
|
62
|
-
- โ
|
|
63
|
-
- โ
Updates `config/
|
|
64
|
-
- โ
Updates `
|
|
57
|
+
- โ
Creates migrations for prefixed tables (`polylingo_chat_conversations`, `polylingo_chat_participants`, `polylingo_chat_messages`, `polylingo_chat_message_translations`)
|
|
58
|
+
- โ
Generates namespaced models in `app/models/polylingo_chat/` directory
|
|
59
|
+
- โ
Generates namespaced controllers in `app/controllers/polylingo_chat/` directory
|
|
60
|
+
- โ
Sets up ActionCable channels (PolylinguoChatChannel) for full-stack apps
|
|
61
|
+
- โ
Creates JavaScript files for real-time chat (full-stack only)
|
|
62
|
+
- โ
Configures Solid Cable in `config/cable.yml` (full-stack only)
|
|
63
|
+
- โ
Downloads ActionCable ESM module to `vendor/javascript` (full-stack only)
|
|
64
|
+
- โ
Updates `config/routes.rb` with namespaced routes
|
|
65
|
+
- โ
Updates `config/importmap.rb` with required pins (full-stack only)
|
|
66
|
+
- โ
Updates `app/javascript/application.js` with chat import (full-stack only)
|
|
65
67
|
- โ
Creates `config/initializers/polylingo_chat.rb` for configuration
|
|
66
68
|
|
|
69
|
+
**Note:** Everything is fully namespaced:
|
|
70
|
+
- Models: `PolylingoChat::Conversation`, `PolylingoChat::Participant`, `PolylingoChat::Message`
|
|
71
|
+
- Controllers: `PolylingoChat::ConversationsController`, `PolylingoChat::MessagesController`
|
|
72
|
+
- Tables: `polylingo_chat_conversations`, `polylingo_chat_participants`, `polylingo_chat_messages`
|
|
73
|
+
- Routes: `/polylingo_chat/conversations`, `/polylingo_chat/messages`
|
|
74
|
+
|
|
67
75
|
### 3. Configure ActiveJob
|
|
68
76
|
```ruby
|
|
69
77
|
# config/application.rb or config/environments/production.rb
|
|
@@ -143,8 +151,198 @@ bundle exec sidekiq # or bin/rails solid_queue:start, or bin/rails jobs:work
|
|
|
143
151
|
|
|
144
152
|
---
|
|
145
153
|
|
|
154
|
+
## ๐ ๏ธ Advanced Installation
|
|
155
|
+
|
|
156
|
+
### API-Only Mode
|
|
157
|
+
|
|
158
|
+
PolylingoChat automatically detects Rails API applications and skips ActionCable/frontend setup. You can also explicitly enable API-only mode:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
bin/rails generate polylingo_chat:install --api-only
|
|
162
|
+
bin/rails db:migrate
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**API-only installation:**
|
|
166
|
+
- โ
Creates migrations and models
|
|
167
|
+
- โ
Creates API controllers (`Api::ConversationsController`, `Api::MessagesController`)
|
|
168
|
+
- โ
Adds API routes under `/api` namespace
|
|
169
|
+
- โ Skips ActionCable channels
|
|
170
|
+
- โ Skips JavaScript files
|
|
171
|
+
- โ Skips Solid Cable setup
|
|
172
|
+
|
|
173
|
+
### Using Polymorphic Associations
|
|
174
|
+
|
|
175
|
+
PolylingoChat supports multiple participant types out of the box. Instead of limiting conversations to just Users, you can have Vendors, Customers, Admins, or any other model as participants.
|
|
176
|
+
|
|
177
|
+
#### Example: Multi-Model Participants
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
# Create a conversation
|
|
181
|
+
conversation = PolylingoChat::Conversation.create!(title: "Support Ticket #123")
|
|
182
|
+
|
|
183
|
+
# Add different types of participants
|
|
184
|
+
user = User.find(1)
|
|
185
|
+
vendor = Vendor.find(5)
|
|
186
|
+
admin = Admin.find(2)
|
|
187
|
+
|
|
188
|
+
conversation.add_participant(user, role: 'customer')
|
|
189
|
+
conversation.add_participant(vendor, role: 'vendor')
|
|
190
|
+
conversation.add_participant(admin, role: 'support')
|
|
191
|
+
|
|
192
|
+
# Get all participants regardless of type
|
|
193
|
+
conversation.participantables
|
|
194
|
+
# => [#<User id: 1>, #<Vendor id: 5>, #<Admin id: 2>]
|
|
195
|
+
|
|
196
|
+
# Get participants of a specific type
|
|
197
|
+
conversation.participantables_of_type(Vendor)
|
|
198
|
+
# => [#<Vendor id: 5>]
|
|
199
|
+
|
|
200
|
+
# Backward compatibility - still works with User model
|
|
201
|
+
conversation.users # Returns all User participants
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
#### Sending Messages with Polymorphic Senders
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
# Any model can send a message
|
|
208
|
+
vendor = Vendor.find(5)
|
|
209
|
+
PolylingoChat::Message.create!(
|
|
210
|
+
conversation: conversation,
|
|
211
|
+
sender: vendor, # Polymorphic - can be User, Vendor, Customer, etc.
|
|
212
|
+
body: "We've shipped your order!"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Message model automatically handles sender_name
|
|
216
|
+
message.sender_name # Tries: name, full_name, email, or "Unknown"
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
146
221
|
## ๐ฏ Usage
|
|
147
222
|
|
|
223
|
+
### Using the API Endpoints
|
|
224
|
+
|
|
225
|
+
PolylingoChat provides JSON API endpoints that work for both API-only and full-stack Rails applications:
|
|
226
|
+
|
|
227
|
+
#### Create a Conversation
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
POST /polylingo_chat/conversations
|
|
231
|
+
Content-Type: application/json
|
|
232
|
+
|
|
233
|
+
{
|
|
234
|
+
"conversation": {
|
|
235
|
+
"title": "Project Discussion"
|
|
236
|
+
},
|
|
237
|
+
"participant_ids": [
|
|
238
|
+
{ "type": "User", "id": 1, "role": "owner" },
|
|
239
|
+
{ "type": "Vendor", "id": 5, "role": "participant" }
|
|
240
|
+
]
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
#### Send a Message
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
POST /polylingo_chat/conversations/:conversation_id/messages
|
|
248
|
+
Content-Type: application/json
|
|
249
|
+
|
|
250
|
+
{
|
|
251
|
+
"message": {
|
|
252
|
+
"body": "Hello! How are you?",
|
|
253
|
+
"language": "en"
|
|
254
|
+
},
|
|
255
|
+
"sender_type": "User",
|
|
256
|
+
"sender_id": 1
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
#### Get Conversation with Messages
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Get conversation without specific language
|
|
264
|
+
GET /polylingo_chat/conversations/:id.json
|
|
265
|
+
|
|
266
|
+
# Get conversation with Spanish translations
|
|
267
|
+
GET /polylingo_chat/conversations/:id.json?lang=es
|
|
268
|
+
|
|
269
|
+
Response:
|
|
270
|
+
{
|
|
271
|
+
"id": 1,
|
|
272
|
+
"title": "Project Discussion",
|
|
273
|
+
"participants": [
|
|
274
|
+
{
|
|
275
|
+
"id": 1,
|
|
276
|
+
"type": "User",
|
|
277
|
+
"participant_id": 1,
|
|
278
|
+
"role": "owner",
|
|
279
|
+
"name": "John Doe"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
"id": 2,
|
|
283
|
+
"type": "Vendor",
|
|
284
|
+
"participant_id": 5,
|
|
285
|
+
"role": "participant",
|
|
286
|
+
"name": "ACME Corp"
|
|
287
|
+
}
|
|
288
|
+
],
|
|
289
|
+
"messages": [
|
|
290
|
+
{
|
|
291
|
+
"id": 1,
|
|
292
|
+
"body": "Hello! How are you?",
|
|
293
|
+
"language": "en",
|
|
294
|
+
"translated": true,
|
|
295
|
+
"translation": "ยกHola! ยฟCรณmo estรกs?",
|
|
296
|
+
"translation_language": "es",
|
|
297
|
+
"available_translations": [
|
|
298
|
+
{"language": "es", "text": "ยกHola! ยฟCรณmo estรกs?"},
|
|
299
|
+
{"language": "fr", "text": "Bonjour! Comment allez-vous?"}
|
|
300
|
+
],
|
|
301
|
+
"sender_type": "User",
|
|
302
|
+
"sender_id": 1,
|
|
303
|
+
"sender_name": "John Doe",
|
|
304
|
+
"created_at": "2025-01-15T10:30:00Z"
|
|
305
|
+
}
|
|
306
|
+
]
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
**New in v0.4.0:** The `?lang=` parameter returns cached translations from the database with zero API calls. The `available_translations` array shows all cached translations for each message.
|
|
311
|
+
|
|
312
|
+
#### List All Messages in a Conversation
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
# Get messages without specific language
|
|
316
|
+
GET /polylingo_chat/conversations/:conversation_id/messages.json
|
|
317
|
+
|
|
318
|
+
# Get messages with French translations (from cache)
|
|
319
|
+
GET /polylingo_chat/conversations/:conversation_id/messages.json?lang=fr
|
|
320
|
+
|
|
321
|
+
Response:
|
|
322
|
+
[
|
|
323
|
+
{
|
|
324
|
+
"id": 1,
|
|
325
|
+
"body": "Hello! How are you?",
|
|
326
|
+
"language": "en",
|
|
327
|
+
"translated": true,
|
|
328
|
+
"translation": "Bonjour! Comment allez-vous?",
|
|
329
|
+
"translation_language": "fr",
|
|
330
|
+
"available_translations": [
|
|
331
|
+
{"language": "es", "text": "ยกHola! ยฟCรณmo estรกs?"},
|
|
332
|
+
{"language": "fr", "text": "Bonjour! Comment allez-vous?"}
|
|
333
|
+
],
|
|
334
|
+
"sender_type": "User",
|
|
335
|
+
"sender_id": 1,
|
|
336
|
+
"sender_name": "John Doe",
|
|
337
|
+
"conversation_id": 1,
|
|
338
|
+
"created_at": "2025-01-15T10:30:00Z",
|
|
339
|
+
"updated_at": "2025-01-15T10:30:05Z"
|
|
340
|
+
}
|
|
341
|
+
]
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Full-Stack Usage (with ActionCable)
|
|
345
|
+
|
|
148
346
|
### 1. Set up your view
|
|
149
347
|
|
|
150
348
|
First, expose the conversation and user IDs to JavaScript in your chat view:
|
|
@@ -165,14 +363,14 @@ First, expose the conversation and user IDs to JavaScript in your chat view:
|
|
|
165
363
|
|
|
166
364
|
```ruby
|
|
167
365
|
# Create a conversation
|
|
168
|
-
conversation = Conversation.create!(title: "Project Discussion")
|
|
366
|
+
conversation = PolylingoChat::Conversation.create!(title: "Project Discussion")
|
|
169
367
|
|
|
170
368
|
# Add participants
|
|
171
|
-
|
|
172
|
-
|
|
369
|
+
conversation.add_participant(user1, role: 'member')
|
|
370
|
+
conversation.add_participant(user2, role: 'member')
|
|
173
371
|
|
|
174
372
|
# Send a message
|
|
175
|
-
Message.create!(
|
|
373
|
+
PolylingoChat::Message.create!(
|
|
176
374
|
conversation: conversation,
|
|
177
375
|
sender: user1,
|
|
178
376
|
body: "Hello! How are you?"
|
|
@@ -280,25 +478,128 @@ Get key: https://ai.google.dev/
|
|
|
280
478
|
|
|
281
479
|
---
|
|
282
480
|
|
|
481
|
+
## ๐พ Translation Caching (New in v0.4.0)
|
|
482
|
+
|
|
483
|
+
PolylingoChat now includes **database-backed translation caching** that dramatically reduces AI API costs and improves performance.
|
|
484
|
+
|
|
485
|
+
### How Translation Caching Works
|
|
486
|
+
|
|
487
|
+
1. **First Time Translation:**
|
|
488
|
+
- User sends a message โ AI translates it for each participant's language
|
|
489
|
+
- Translations are saved to `polylingo_chat_message_translations` table
|
|
490
|
+
- One translation per language per message
|
|
491
|
+
|
|
492
|
+
2. **Subsequent Retrievals:**
|
|
493
|
+
- Page reloads, API calls โ translations loaded from database
|
|
494
|
+
- **Zero AI API calls** for cached translations
|
|
495
|
+
- Instant response times
|
|
496
|
+
|
|
497
|
+
### Benefits
|
|
498
|
+
|
|
499
|
+
- โ
**Massive cost savings** - Translate once, retrieve unlimited times
|
|
500
|
+
- โ
**Lightning fast** - Database queries vs AI API calls
|
|
501
|
+
- โ
**Works offline** - No dependency on AI service availability for viewing
|
|
502
|
+
- โ
**Perfect for mobile** - Mobile apps get instant cached translations
|
|
503
|
+
- โ
**Scales effortlessly** - Database caching scales with your infrastructure
|
|
504
|
+
|
|
505
|
+
### API Usage Example
|
|
506
|
+
|
|
507
|
+
```bash
|
|
508
|
+
# First time - translates and caches
|
|
509
|
+
curl -X POST http://localhost:3000/polylingo_chat/conversations/1/messages.json \
|
|
510
|
+
-H "Content-Type: application/json" \
|
|
511
|
+
-d '{"message":{"body":"Hello!"},"sender_type":"User","sender_id":1}'
|
|
512
|
+
|
|
513
|
+
# Response includes available_translations
|
|
514
|
+
{
|
|
515
|
+
"id": 15,
|
|
516
|
+
"body": "Hello!",
|
|
517
|
+
"language": "en",
|
|
518
|
+
"available_translations": [
|
|
519
|
+
{"language": "es", "text": "ยกHola!"},
|
|
520
|
+
{"language": "fr", "text": "Bonjour!"}
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
# Later retrievals - instant, no AI API calls
|
|
525
|
+
curl http://localhost:3000/polylingo_chat/conversations/1/messages.json?lang=es
|
|
526
|
+
|
|
527
|
+
# Returns cached Spanish translation immediately
|
|
528
|
+
{
|
|
529
|
+
"translation": "ยกHola!",
|
|
530
|
+
"translation_language": "es",
|
|
531
|
+
"available_translations": [...]
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
### Database Schema
|
|
536
|
+
|
|
537
|
+
The `polylingo_chat_message_translations` table stores all cached translations:
|
|
538
|
+
|
|
539
|
+
```ruby
|
|
540
|
+
# Schema
|
|
541
|
+
create_table :polylingo_chat_message_translations do |t|
|
|
542
|
+
t.references :message, null: false
|
|
543
|
+
t.string :language, null: false # ISO 639-1 code (e.g., 'es', 'fr')
|
|
544
|
+
t.text :translated_text, null: false
|
|
545
|
+
t.timestamps
|
|
546
|
+
|
|
547
|
+
t.index [:message_id, :language], unique: true
|
|
548
|
+
end
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
283
553
|
## ๐๏ธ How It Works
|
|
284
554
|
|
|
285
555
|
### Message Flow
|
|
286
556
|
|
|
287
557
|
1. User sends a message โ saved to database
|
|
288
|
-
2. `Message#after_create_commit` enqueues job
|
|
558
|
+
2. `Message#after_create_commit` enqueues translation job
|
|
289
559
|
3. Job runs in background via your ActiveJob adapter
|
|
290
|
-
4. **If API key present:**
|
|
560
|
+
4. **If API key present:**
|
|
561
|
+
- Message translated for each participant's language
|
|
562
|
+
- Translations saved to `message_translations` table (cached)
|
|
563
|
+
- Future retrievals use cached translations (zero API calls)
|
|
291
564
|
5. **If no API key:** Original message used for all participants
|
|
292
565
|
6. Messages broadcast via ActionCable (using Solid Cable)
|
|
293
566
|
7. Recipients see messages in real-time
|
|
294
567
|
|
|
295
568
|
### Models
|
|
296
569
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
**
|
|
300
|
-
|
|
301
|
-
|
|
570
|
+
All models are namespaced under `PolylingoChat::` and use the `polylingo_chat_` table prefix to prevent conflicts:
|
|
571
|
+
|
|
572
|
+
**PolylingoChat::Conversation** - Chat conversation with many participants and messages
|
|
573
|
+
- Table: `polylingo_chat_conversations`
|
|
574
|
+
- Location: `app/models/polylingo_chat/conversation.rb`
|
|
575
|
+
- `participantables` - Returns all participant records (polymorphic)
|
|
576
|
+
- `participantables_of_type(klass)` - Returns participants of a specific type
|
|
577
|
+
- `users` - Returns User participants (backward compatibility)
|
|
578
|
+
- `add_participant(record, role: nil)` - Adds any model as a participant
|
|
579
|
+
- `includes_participant?(record)` - Checks if a record is a participant
|
|
580
|
+
|
|
581
|
+
**PolylingoChat::Participant** - Join table with polymorphic associations
|
|
582
|
+
- Table: `polylingo_chat_participants`
|
|
583
|
+
- Location: `app/models/polylingo_chat/participant.rb`
|
|
584
|
+
- `participantable` - The actual participant (User, Vendor, Customer, etc.)
|
|
585
|
+
- Supports multiple model types via polymorphic association
|
|
586
|
+
- `user` alias for backward compatibility
|
|
587
|
+
|
|
588
|
+
**PolylingoChat::Message** - Individual chat message with translation tracking
|
|
589
|
+
- Table: `polylingo_chat_messages`
|
|
590
|
+
- Location: `app/models/polylingo_chat/message.rb`
|
|
591
|
+
- `sender` - Polymorphic association (User, Vendor, Customer, etc.)
|
|
592
|
+
- `sender_name` - Helper method that works with any sender type
|
|
593
|
+
- `translations` - Has many MessageTranslation records (cached translations)
|
|
594
|
+
- `translation_for(language)` - Returns cached translation or original body
|
|
595
|
+
- Automatic translation via background job if API key present
|
|
596
|
+
|
|
597
|
+
**PolylingoChat::MessageTranslation** - Cached translations (New in v0.4.0)
|
|
598
|
+
- Table: `polylingo_chat_message_translations`
|
|
599
|
+
- Location: `app/models/polylingo_chat/message_translation.rb`
|
|
600
|
+
- Stores one translation per language per message
|
|
601
|
+
- Unique index on `[message_id, language]`
|
|
602
|
+
- Dramatically reduces AI API costs by caching translations
|
|
302
603
|
|
|
303
604
|
### Solid Cable (Database-Backed WebSockets)
|
|
304
605
|
|
|
@@ -395,6 +696,66 @@ Perfect for:
|
|
|
395
696
|
- Multi-language support systems
|
|
396
697
|
- Cross-border collaboration
|
|
397
698
|
|
|
699
|
+
### API-Only Mode
|
|
700
|
+
Perfect for:
|
|
701
|
+
- Mobile app backends (iOS, Android, React Native)
|
|
702
|
+
- SPA frontends (React, Vue, Angular)
|
|
703
|
+
- Microservices architectures
|
|
704
|
+
- Custom WebSocket implementations
|
|
705
|
+
- Multi-platform applications
|
|
706
|
+
|
|
707
|
+
### Polymorphic Participants
|
|
708
|
+
Perfect for:
|
|
709
|
+
- Marketplace chats (Buyers โ Sellers โ Support)
|
|
710
|
+
- Multi-tenant systems (Users โ Vendors โ Admins)
|
|
711
|
+
- B2B platforms (Companies โ Representatives)
|
|
712
|
+
- Healthcare apps (Patients โ Doctors โ Nurses)
|
|
713
|
+
|
|
714
|
+
---
|
|
715
|
+
|
|
716
|
+
## ๐ Frontend Integration
|
|
717
|
+
|
|
718
|
+
PolylingoChat works seamlessly with frontend frameworks like **React**, **Next.js**, **Vue**, **Angular**, and more.
|
|
719
|
+
|
|
720
|
+
For a comprehensive guide on integrating with frontend applications, including:
|
|
721
|
+
- Complete API endpoint documentation
|
|
722
|
+
- React component examples with TypeScript
|
|
723
|
+
- Next.js integration patterns
|
|
724
|
+
- Real-time update strategies (polling, WebSocket, third-party services)
|
|
725
|
+
- Authentication patterns
|
|
726
|
+
- Translation query parameters
|
|
727
|
+
|
|
728
|
+
See the **[Frontend Integration Guide](FRONTEND_INTEGRATION.md)** for complete details.
|
|
729
|
+
|
|
730
|
+
### Quick Example (React + TypeScript)
|
|
731
|
+
|
|
732
|
+
```typescript
|
|
733
|
+
// Fetch messages with translation
|
|
734
|
+
const messages = await fetch(
|
|
735
|
+
`/polylingo_chat/conversations/1/messages?translate=true&target_language=es`,
|
|
736
|
+
{
|
|
737
|
+
headers: {
|
|
738
|
+
'Authorization': `Bearer ${token}`,
|
|
739
|
+
'Content-Type': 'application/json'
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
).then(r => r.json());
|
|
743
|
+
|
|
744
|
+
// Send a message
|
|
745
|
+
await fetch('/polylingo_chat/conversations/1/messages', {
|
|
746
|
+
method: 'POST',
|
|
747
|
+
headers: {
|
|
748
|
+
'Authorization': `Bearer ${token}`,
|
|
749
|
+
'Content-Type': 'application/json'
|
|
750
|
+
},
|
|
751
|
+
body: JSON.stringify({
|
|
752
|
+
message: { body: "Hello!" },
|
|
753
|
+
sender_type: "User",
|
|
754
|
+
sender_id: 1
|
|
755
|
+
})
|
|
756
|
+
});
|
|
757
|
+
```
|
|
758
|
+
|
|
398
759
|
---
|
|
399
760
|
|
|
400
761
|
## ๐ค Contributing
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
===============================================================================
|
|
2
|
+
|
|
3
|
+
PolylingoChat has been installed successfully!
|
|
4
|
+
|
|
5
|
+
Models, Controllers, Views, and Jobs are provided by the gem (like Devise).
|
|
6
|
+
They are located in the gem's app/ directory and can be overridden by creating
|
|
7
|
+
files with the same path in your application's app/ directory.
|
|
8
|
+
|
|
9
|
+
Next steps:
|
|
10
|
+
|
|
11
|
+
1. Run migrations:
|
|
12
|
+
rails db:migrate
|
|
13
|
+
|
|
14
|
+
2. Ensure your User model has a `preferred_language` column (optional):
|
|
15
|
+
rails g migration AddPreferredLanguageToUsers preferred_language:string
|
|
16
|
+
rails db:migrate
|
|
17
|
+
|
|
18
|
+
3. Configure your AI provider in config/initializers/polylingo_chat.rb:
|
|
19
|
+
- Set config.api_key with your API key
|
|
20
|
+
- Choose your provider (:openai, :anthropic, or :gemini)
|
|
21
|
+
- Or leave api_key as nil for chat-only mode (no translation)
|
|
22
|
+
|
|
23
|
+
4. Set up Solid Cable for production:
|
|
24
|
+
- Already configured for development
|
|
25
|
+
- For production, ensure database is configured in config/cable.yml
|
|
26
|
+
|
|
27
|
+
5. For ActionCable/real-time chat (full-stack apps):
|
|
28
|
+
# Already set up automatically during installation
|
|
29
|
+
# JavaScript files and channels are configured
|
|
30
|
+
|
|
31
|
+
6. Add this to your User model:
|
|
32
|
+
# app/models/user.rb
|
|
33
|
+
class User < ApplicationRecord
|
|
34
|
+
# Optional: for chat functionality
|
|
35
|
+
has_many :polylingo_chat_participants,
|
|
36
|
+
class_name: "PolylingoChat::Participant",
|
|
37
|
+
as: :participantable,
|
|
38
|
+
dependent: :destroy
|
|
39
|
+
has_many :polylingo_chat_conversations,
|
|
40
|
+
through: :polylingo_chat_participants,
|
|
41
|
+
source: :conversation
|
|
42
|
+
has_many :polylingo_chat_messages,
|
|
43
|
+
class_name: "PolylingoChat::Message",
|
|
44
|
+
as: :sender,
|
|
45
|
+
dependent: :destroy
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
7. To customize models, controllers, or views, create files in your app:
|
|
49
|
+
# app/models/polylingo_chat/conversation.rb
|
|
50
|
+
module PolylingoChat
|
|
51
|
+
class Conversation < ApplicationRecord
|
|
52
|
+
# Your custom code here - this will override the gem's version
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# app/controllers/polylingo_chat/conversations_controller.rb
|
|
57
|
+
module PolylingoChat
|
|
58
|
+
class ConversationsController < ApplicationController
|
|
59
|
+
# Your custom code here - this will override the gem's version
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
8. Visit your conversations:
|
|
64
|
+
- HTML: http://localhost:3000/polylingo_chat/conversations
|
|
65
|
+
- JSON API: http://localhost:3000/polylingo_chat/conversations.json
|
|
66
|
+
|
|
67
|
+
For frontend integration (React, Next.js, Vue, Angular):
|
|
68
|
+
See: FRONTEND_INTEGRATION.md in the gem's directory
|
|
69
|
+
|
|
70
|
+
For more information, visit: https://github.com/AdwareTechnologies/polylingo_chat
|
|
71
|
+
|
|
72
|
+
===============================================================================
|
|
@@ -8,33 +8,37 @@ module PolylingoChat
|
|
|
8
8
|
|
|
9
9
|
source_root File.expand_path('templates', __dir__)
|
|
10
10
|
|
|
11
|
+
class_option :api_only, type: :boolean, default: nil,
|
|
12
|
+
desc: "Install for API-only Rails app (skip ActionCable/frontend)"
|
|
13
|
+
|
|
11
14
|
desc "Installs PolylingoChat with real-time multilingual chat"
|
|
12
15
|
|
|
16
|
+
def initialize(*args)
|
|
17
|
+
super
|
|
18
|
+
@api_only = detect_api_only_mode
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
def self.next_migration_number(dirname)
|
|
14
22
|
next_migration_number = current_migration_number(dirname) + 1
|
|
15
23
|
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
|
16
24
|
end
|
|
17
25
|
|
|
18
26
|
def copy_migrations
|
|
19
|
-
migration_template "create_conversations.rb", "db/migrate/
|
|
20
|
-
migration_template "create_participants.rb", "db/migrate/
|
|
21
|
-
migration_template "create_messages.rb", "db/migrate/
|
|
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"
|
|
27
|
+
migration_template "create_conversations.rb", "db/migrate/create_polylingo_chat_conversations.rb"
|
|
28
|
+
migration_template "create_participants.rb", "db/migrate/create_polylingo_chat_participants.rb"
|
|
29
|
+
migration_template "create_messages.rb", "db/migrate/create_polylingo_chat_messages.rb"
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def create_channels
|
|
33
|
+
return if @api_only
|
|
31
34
|
empty_directory "app/channels/application_cable"
|
|
32
35
|
template "channels/application_cable/channel.rb", "app/channels/application_cable/channel.rb"
|
|
33
36
|
template "channels/application_cable/connection.rb", "app/channels/application_cable/connection.rb"
|
|
34
|
-
template "channels/
|
|
37
|
+
template "channels/polylingo_chat_channel.rb", "app/channels/polylingo_chat_channel.rb"
|
|
35
38
|
end
|
|
36
39
|
|
|
37
40
|
def create_javascript_files
|
|
41
|
+
return if @api_only
|
|
38
42
|
empty_directory "app/javascript/channels"
|
|
39
43
|
template "javascript/chat.js", "app/javascript/chat.js"
|
|
40
44
|
template "javascript/channels/consumer.js", "app/javascript/channels/consumer.js"
|
|
@@ -42,6 +46,7 @@ module PolylingoChat
|
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
def configure_cable
|
|
49
|
+
return if @api_only
|
|
45
50
|
if File.exist?("config/cable.yml")
|
|
46
51
|
gsub_file "config/cable.yml", /^development:\s*\n\s+adapter:\s+\w+.*$/m do |match|
|
|
47
52
|
"development:\n adapter: solid_cable\n polling_interval: 0.1.seconds\n message_retention: 1.day"
|
|
@@ -50,26 +55,28 @@ module PolylingoChat
|
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
def setup_solid_cable
|
|
58
|
+
return if @api_only
|
|
53
59
|
say "Setting up Solid Cable...", :green
|
|
54
60
|
rails_command "solid_cable:install", abort_on_failure: false
|
|
55
61
|
end
|
|
56
62
|
|
|
57
63
|
def download_actioncable
|
|
64
|
+
return if @api_only
|
|
58
65
|
empty_directory "vendor/javascript"
|
|
59
66
|
say "Downloading ActionCable ESM module...", :green
|
|
60
67
|
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
68
|
end
|
|
62
69
|
|
|
63
70
|
def add_routes
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
RUBY
|
|
71
|
+
unless @api_only
|
|
72
|
+
route 'mount ActionCable.server => "/cable"'
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
route 'mount PolylingoChat::Engine => "/polylingo_chat"'
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
def update_importmap
|
|
79
|
+
return if @api_only
|
|
73
80
|
if File.exist?("config/importmap.rb")
|
|
74
81
|
append_to_file "config/importmap.rb" do
|
|
75
82
|
<<~RUBY
|
|
@@ -84,6 +91,7 @@ module PolylingoChat
|
|
|
84
91
|
end
|
|
85
92
|
|
|
86
93
|
def update_application_js
|
|
94
|
+
return if @api_only
|
|
87
95
|
if File.exist?("app/javascript/application.js")
|
|
88
96
|
append_to_file "app/javascript/application.js" do
|
|
89
97
|
<<~JS
|
|
@@ -102,6 +110,26 @@ module PolylingoChat
|
|
|
102
110
|
def show_readme
|
|
103
111
|
readme "README" if behavior == :invoke
|
|
104
112
|
end
|
|
113
|
+
|
|
114
|
+
private
|
|
115
|
+
|
|
116
|
+
def detect_api_only_mode
|
|
117
|
+
# Use explicit option if provided
|
|
118
|
+
return options[:api_only] unless options[:api_only].nil?
|
|
119
|
+
|
|
120
|
+
# Auto-detect: Check if this is an API-only Rails app
|
|
121
|
+
# API-only apps typically don't have ActionView or have it explicitly disabled
|
|
122
|
+
api_only = !defined?(ActionView::Base) ||
|
|
123
|
+
(defined?(Rails.application) && Rails.application.config.respond_to?(:api_only) && Rails.application.config.api_only)
|
|
124
|
+
|
|
125
|
+
if api_only
|
|
126
|
+
say "Detected API-only Rails application. Skipping ActionCable/frontend setup.", :yellow
|
|
127
|
+
else
|
|
128
|
+
say "Installing with full-stack support (ActionCable + frontend).", :green
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
api_only
|
|
132
|
+
end
|
|
105
133
|
end
|
|
106
134
|
end
|
|
107
135
|
end
|