second commit
This commit is contained in:
@@ -0,0 +1,283 @@
|
||||
# GoChat vs Chatwoot 全面对比 — 1:1 功能复制差距分析
|
||||
|
||||
> 生成日期:2026-05-24
|
||||
> 目标:评估GoChat是否达到Chatwoot 1:1功能复制,具备生产级上线能力
|
||||
|
||||
---
|
||||
|
||||
## 一、总览对比
|
||||
|
||||
| 指标 | Chatwoot | GoChat | 差距 |
|
||||
|------|---------|--------|------|
|
||||
| API路由数 | 327+ | 106 | **缺221+路由 (仅32%覆盖)** |
|
||||
| Handler方法数 | ~150+ | 99 | **缺51+方法** |
|
||||
| 真实实现率 | 100% | 57% | **差43%** |
|
||||
| Stub/Placeholder | 0 | 37个 | **全部需补实现** |
|
||||
| 501 Not Implemented | 0 | 6个 | **全部需补实现** |
|
||||
| 消息队列调度 | Sidekiq完整 | WorkerPool全stub | **不可用** |
|
||||
| 渠道扩展机制 | 11渠道+Channelable | 注册机制有+仅2渠道 | **缺9渠道** |
|
||||
| 事件分发 | Dispatcher+9 Listener | Dispatcher骨架+无真实Listener | **事件驱动完全不可用** |
|
||||
|
||||
---
|
||||
|
||||
## 二、按模块详细对比
|
||||
|
||||
### ✅ 完全实现模块(11个,56个方法)
|
||||
|
||||
| 模块 | 方法数 | 实现率 | 说明 |
|
||||
|------|--------|--------|------|
|
||||
| Auth | 8 | 100% | Login/MFA/Register/Refresh/Logout/SwitchAccount/ResetPassword/ConfirmEmail |
|
||||
| MFA | 4 | 100% | Enable/Verify/Disable/GetStatus |
|
||||
| Captain Assistant | 9 | 100% | 完整CRUD + Search + Inbox关联 |
|
||||
| Captain Document | 5 | 100% | CRUD + Search |
|
||||
| Captain Scenario | 5 | 100% | 完整CRUD |
|
||||
| Captain Custom Tool | 5 | 100% | 完整CRUD |
|
||||
| Copilot | 3 | 100% | SuggestReplies/Summarize/Translate |
|
||||
| WebWidget | 3 | 100% | GetConfig/InitConversation/SendMessage |
|
||||
| Telegram Channel | 4 | 100% | Configure/Deauthorize/Reauthorize/GetInfo |
|
||||
| Analytics | 6 | 100% | Summary + 5类Metrics |
|
||||
| LiveReport | 3 | 100% | AccountSummary + AgentConversation + Labels |
|
||||
|
||||
### ⚠️ 部分实现模块(2个)
|
||||
|
||||
| 模块 | Real | Stub | 差距说明 |
|
||||
|------|------|------|----------|
|
||||
| Account | 1 (Delete) | 4 (List/Get/Create/Update) | CRUD全stub,仅Delete有真实实现 |
|
||||
| SAML | 1 (Enable) | 2 (GetConfig/InitiateLogin) | 缺SSO登录流程 |
|
||||
|
||||
### ❌ 全Stub模块(9个,43个方法)—— **生产上线最大障碍**
|
||||
|
||||
| 模块 | Stub方法 | 501方法 | Chatwoot对应功能 | 严重性 |
|
||||
|------|----------|---------|-----------------|--------|
|
||||
| **Contact** | 4 (List/Get/Create/Search) | 2 (Update/Delete) | M4-联系人管理 | 🔴 致命 |
|
||||
| **Conversation** | 4 (List/Get/Create/UpdateLabels) | 1 (Update) | M3-对话核心 | 🔴 致命 |
|
||||
| **Message** | 3 (List/Create/Search) | 0 | M3-消息核心 | 🔴 致命 |
|
||||
| **Inbox** | 3 (List/Get/Create) | 2 (Update/Delete) | M2-渠道管理 | 🔴 致命 |
|
||||
| **Profile** | 3 (Get/Update/DeleteAvatar) | 0 | M1-用户设置 | 🟡 重要 |
|
||||
| **Notification** | 6 (List/Get/MarkRead/MarkAllRead/UnreadCount/DeleteAll) | 0 | M8-通知系统 | 🟡 重要 |
|
||||
| **Team** | 4 (List/Create/Get/Update) | 1 (Delete) | M5-团队管理 | 🟡 重要 |
|
||||
| **PlatformApp** | 2 (List/Create) | 0 | M12-平台应用 | 🟢 可后补 |
|
||||
| **DashboardApp** | 2 (List/Create) | 0 | M12-仪表盘 | 🟢 可后补 |
|
||||
|
||||
---
|
||||
|
||||
## 三、消息队列调度系统差距
|
||||
|
||||
### Chatwoot 事件驱动架构
|
||||
|
||||
Chatwoot使用 **Wisper + Sidekiq** 双层事件系统:
|
||||
|
||||
| 层 | 技术 | 功能 | 关键组件 |
|
||||
|----|------|------|---------|
|
||||
| 同步层 | Wisper广播 | 实时事件通知(ActionCable、AgentBot) | SyncDispatcher |
|
||||
| 异步层 | Sidekiq Worker | 持久化异步任务处理 | 9个AsyncDispatcher Listener |
|
||||
| WebSocket | ActionCable | 前端实时推送 | ActionCableListener |
|
||||
| Push通知 | FCM + Web Push | 移动端推送 | PushNotificationListener |
|
||||
| Email | Mailer | 邮件通知 | EmailNotificationListener |
|
||||
|
||||
**Chatwoot 9个异步Listener(全部通过Sidekiq持久队列):**
|
||||
|
||||
1. **AutomationRuleListener** — 事件触发自动化规则执行
|
||||
2. **CampaignListener** — 营销活动消息发送
|
||||
3. **CsatSurveyListener** — 满意度调查推送
|
||||
4. **HookListener** — Integration Hook执行
|
||||
5. **InstallationWebhookListener** — 平台级Webhook投递
|
||||
6. **NotificationListener** — 应用内通知创建
|
||||
7. **ParticipationListener** — 对话参与度更新
|
||||
8. **ReportingEventListener** — 报表数据聚合
|
||||
9. **WebhookListener** — Account Webhook投递
|
||||
|
||||
### GoChat 当前状态
|
||||
|
||||
| 层 | 技术 | 状态 | 差距 |
|
||||
|----|------|------|------|
|
||||
| 事件总线 | Watermill + Redis Streams | **骨架有,未接入业务** | EventBus定义了topic常量但无真实subscriber |
|
||||
| PubSub接口 | RedisPubSub + InMemoryPubSub | **接口完整,无业务调用** | Publish/Subscribe可用但service层从不调用 |
|
||||
| Dispatcher | channel.Dispatcher + dispatch.Dispatcher | **注册机制有,无真实Listener** | Dispatcher.Register可注册但无业务Listener实现 |
|
||||
| WorkerPool | 全stub | **不可用** | Start/Stop返回nil,无任何job处理 |
|
||||
| WebSocket | ws包 | **骨架有,无消息关联** | broadcast/presence/typing有但未与pubsub联动 |
|
||||
|
||||
**关键缺失:整个事件流转链路断裂**
|
||||
|
||||
```
|
||||
Chatwoot完整链路:
|
||||
用户操作 → Event → Wisper广播 → SyncDispatcher(实时) + Sidekiq(异步)
|
||||
→ AutomationRuleListener → 执行规则 → 发消息/改状态/分配坐席
|
||||
→ NotificationListener → 创建通知 → ActionCable推送前端
|
||||
→ WebhookListener → HTTP POST到外部系统
|
||||
|
||||
GoChat当前链路:
|
||||
用户操作 → handler stub → 返回placeholder JSON → 结束(无事件发布)
|
||||
```
|
||||
|
||||
**差距量化:**
|
||||
- Chatwoot 9个Async Listener → GoChat 0个
|
||||
- Chatwoot Sidekiq 6个Worker类型 → GoChat WorkerPool全stub
|
||||
- Chatwoot ActionCable 15+事件频道 → GoChat WebSocket未接入pubsub
|
||||
- Chatwoot Webhook投递(签名+重试+去重) → GoChat仅webhook_auth中间件
|
||||
|
||||
---
|
||||
|
||||
## 四、可扩展Inbox机制差距
|
||||
|
||||
### Chatwoot Channelable架构
|
||||
|
||||
Chatwoot采用 **多态 + Concern** 模式,支持11种渠道类型:
|
||||
|
||||
| 渠道类型 | Channel模型 | 状态 | 特殊功能 |
|
||||
|---------|-----------|------|---------|
|
||||
| Channel::WebWidget | channel_web_widgets | ✅ | 实时聊天+离线消息+欢迎语 |
|
||||
| Channel::Telegram | channel_telegram | ✅ | Bot API+Webhook回调 |
|
||||
| Channel::FacebookPage | channel_facebook_pages | ✅ | Messenger+OAuth刷新 |
|
||||
| Channel::Instagram | channel_instagram | ✅ | DM+评论+OAuth |
|
||||
| Channel::WhatsApp | channel_whatsapp | ✅ | Meta Business API+Baileys |
|
||||
| Channel::Email | channel_email | ✅ | IMAP接收+SMTP发送 |
|
||||
| Channel::TwilioSMS | channel_twilio_sms | ✅ | Twilio API |
|
||||
| Channel::TwilioWhatsApp | channel_twilio_whatsapp | ✅ | Twilio WA API |
|
||||
| Channel::Line | channel_line | ✅ | LINE Messaging API |
|
||||
| Channel::Slack | channel_slack | ✅ | Slack Events API |
|
||||
| Channel::Api | channel_api | ✅ | REST API接入 |
|
||||
|
||||
**Chatwoot Inbox扩展性关键机制:**
|
||||
|
||||
1. **Channelable concern** — 统一接口(name/avatar_type/revalidate_credentials等)
|
||||
2. **多态关联** — Inbox belongs_to channel, polymorphic: true
|
||||
3. **渠道创建流程** — InboxBuilder根据channel_type选择对应Builder
|
||||
4. **IncomingMessageService** — 根据channel_type路由到对应处理逻辑
|
||||
5. **OutgoingMessageService** — 根据channel_type选择发送渠道
|
||||
6. **OAuth刷新** — Reauthorizable concern(FB/Instagram定期刷新token)
|
||||
7. **Webhook回调** — 每个渠道有独立webhook endpoint
|
||||
8. **渠道设置** — 每个渠道有独立设置页面和配置字段
|
||||
|
||||
### GoChat 当前状态
|
||||
|
||||
| 机制 | 状态 | 差距 |
|
||||
|------|------|------|
|
||||
| ChannelRegistry | ✅ 完整实现 | 注册/获取/列举provider均可 |
|
||||
| ChannelProvider接口 | ✅ 完整定义 | Type/Name/ConfigSchema/ValidateConfig/ProcessIncoming/SendOutgoing等 |
|
||||
| ChannelType枚举 | ✅ 定义了11种 | 但仅2种有真实provider |
|
||||
| WebWidget provider | ✅ 完整实现 | pipeline+service+repository |
|
||||
| Telegram provider | ✅ 完整实现 | pipeline+service+repository+webhook_handler |
|
||||
| Facebook provider | ❌ 无实现 | model文件有但channel/provider/facebook.go缺失 |
|
||||
| WhatsApp provider | ❌ 无实现 | model文件有但无provider |
|
||||
| Instagram provider | ❌ 无实现 | 无任何文件 |
|
||||
| Email provider | ❌ 无实现 | 无任何文件 |
|
||||
| TwilioSMS/WA provider | ❌ 无实现 | 无任何文件 |
|
||||
| Line provider | ❌ 无实现 | 无任何文件 |
|
||||
| Slack provider | ❌ 无实现 | 无任何文件 |
|
||||
| API provider | ❌ 无实现 | model文件有(txt)但无provider |
|
||||
| InboxBuilder | ❌ 缺失 | 无根据channel_type选择Builder的逻辑 |
|
||||
| IncomingMessage路由 | ❌ 缺失 | ProcessIncoming在provider接口定义但无真实路由调用 |
|
||||
| OutgoingMessage路由 | ❌ 缺失 | SendOutgoing在provider接口定义但无真实调用 |
|
||||
| OAuth刷新 | ❌ 缺失 | 无Reauthorizable机制 |
|
||||
| 渠道Webhook路由 | ❌ 仅1个stub | `/webhooks/:channel_type/:identifier` placeholder |
|
||||
|
||||
**差距量化:**
|
||||
- 11种渠道类型 → 仅2种有实现 (18%覆盖率)
|
||||
- Inbox CRUD 5方法 → 全stub (0%实现率)
|
||||
- InboxMember坐席绑定 → 无路由、无model关联
|
||||
- 渠道创建流程 → 无InboxBuilder
|
||||
- 消息收发路由 → 无Incoming/Outgoing消息路由到provider
|
||||
|
||||
---
|
||||
|
||||
## 五、Chatwoot完全缺失的API模块(路由级别)
|
||||
|
||||
| # | 缺失模块 | 缺失路由数 | 严重性 |
|
||||
|---|---------|-----------|--------|
|
||||
| 1 | **Agent/AgentBot** | 8+ | 🔴 致命 |
|
||||
| 2 | **Canned Responses** | 4 | 🟡 重要 |
|
||||
| 3 | **Automation Rules** | 6 | 🟡 重要 |
|
||||
| 4 | **Macros** | 5 | 🟡 重要 |
|
||||
| 5 | **SLA Policies** | 5 | 🟡 重要 |
|
||||
| 6 | **Custom Roles** | 5 | 🟡 重要 |
|
||||
| 7 | **Agent Capacity Policies** | 7+ | 🟡 重要 |
|
||||
| 8 | **Assignment Policies** | 5+ | 🔴 致命 |
|
||||
| 9 | **Campaigns** | 5 | 🟢 可后补 |
|
||||
| 10 | **Search(全局搜索)** | 5 | 🔴 致命 |
|
||||
| 11 | **Companies** | 10+ | 🟡 重要 |
|
||||
| 12 | **CSAT Survey Responses** | 4+ | 🟡 重要 |
|
||||
| 13 | **Notes (对话笔记)** | 4 | 🟡 重要 |
|
||||
| 14 | **Labels** | 3 | 🟡 重要 |
|
||||
| 14 | **Attachments** | 3 | 🟡 重要 |
|
||||
| 15 | **Integrations/Apps** | 10+ | 🟢 可后补 |
|
||||
| 16 | **Help Center/Portal** | 15+ | 🟢 可后补 |
|
||||
| 17 | **OAuth回调(FB/Instagram/WA)** | 5+ | 🔴 致命 |
|
||||
| **总计缺失路由** | | **~100+** | |
|
||||
|
||||
---
|
||||
|
||||
## 六、结论与建议
|
||||
|
||||
### 当前状态:**不具备生产级上线能力**
|
||||
|
||||
**三层架构断裂:**
|
||||
|
||||
1. **Handler层断裂** — 9个核心CRM模块全stub(Contact/Conversation/Message/Inbox等),客服系统核心功能完全不可用
|
||||
2. **事件驱动断裂** — EventBus/PubSub/Dispatcher骨架存在但零业务接入,无Listener实现,无异步Worker处理,事件流转链路完全断裂
|
||||
3. **渠道扩展断裂** — 注册机制完整但仅2/11渠道有实现,Inbox CRUD全stub,消息收发路由未接入ChannelProvider
|
||||
|
||||
### 实现到1:1功能复制需要的工作量估算
|
||||
|
||||
| 优先级 | 模块 | 工作内容 | 预估天数 |
|
||||
|--------|------|---------|---------|
|
||||
| P0 致命 | Conversation+Message | 全部handler+service+repo真实实现+状态流转+标签+笔记+附件 | 4-5 |
|
||||
| P0 致命 | Contact+Inbox | 全部handler+service+repo+InboxBuilder+InboxMember+Channelable路由 | 3-4 |
|
||||
| P0 致命 | Account完整CRUD | List/Get/Create/Update真实实现 | 1 |
|
||||
| P0 致命 | Agent/AgentBot+Assignment | 新模块CRUD+自动分配引擎(round-robin/lowest-load) | 3-4 |
|
||||
| P0 致命 | 事件驱动系统 | 9个Async Listener+WorkerPool(asynq)+PubSub→Service联动 | 3-4 |
|
||||
| P0 致命 | 消息收发路由 | Incoming→ChannelProvider.ProcessIncoming+Outgoing→SendOutgoing | 2-3 |
|
||||
| P1 重要 | Notification+Webhook | 全部handler+创建通知+Webhook投递(签名+重试) | 2-3 |
|
||||
| P1 重要 | Team+Profile | 全部handler真实实现 | 1-2 |
|
||||
| P1 重要 | AutomationRule+Macro+CSAT | 新模块CRUD+规则执行引擎 | 2-3 |
|
||||
| P1 重要 | SLA+CustomRole+CapacityPolicy | 新模块CRUD | 2 |
|
||||
| P1 重要 | Search全局搜索 | Elasticsearch/Meilisearch集成 | 1-2 |
|
||||
| P2 可后补 | 渠道集成(FB/WA/Email) | 9个Channel Provider实现 | 5-8 |
|
||||
| P2 可后补 | Companies+Campaign+HelpCenter | 新模块 | 3-4 |
|
||||
| **总计** | | **约130+方法/API** | **~25-35天** |
|
||||
|
||||
### 建议执行策略
|
||||
|
||||
**P0阶段(致命缺口,约15天):先让客服系统"能跑起来"**
|
||||
|
||||
1. Conversation+Message核心 — 对话创建+状态流转+消息收发
|
||||
2. Contact+Inbox核心 — 联系人管理+收件箱CRUD+渠道绑定
|
||||
3. 事件驱动接入 — PubSub→Service联动+WorkerPool(asynq)
|
||||
4. 消息收发路由 — Incoming/Outgoing→ChannelProvider
|
||||
5. Agent+自动分配 — 坐席管理+round-robin分配
|
||||
|
||||
**P1阶段(运营必需,约10天):让系统"好用"**
|
||||
|
||||
6. Notification+Webhook — 坐席感知+外部集成
|
||||
7. Automation+Macro+CSAT — 自动化+满意度
|
||||
8. SLA+Role+Capacity — 企业级管理
|
||||
9. Team+Profile+Search — 团队协作+信息检索
|
||||
|
||||
**P2阶段(可迭代,约10天):让系统"完整"**
|
||||
|
||||
10. 9个渠道Provider实现
|
||||
11. Companies+Campaign+HelpCenter
|
||||
12. OAuth刷新+更多渠道设置
|
||||
|
||||
---
|
||||
|
||||
## 附录:关键代码文件索引
|
||||
|
||||
| 功能 | GoChat文件 | 状态 |
|
||||
|------|-----------|------|
|
||||
| PubSub接口 | internal/pubsub/pubsub.go | ✅ 接口完整 |
|
||||
| Redis PubSub | internal/pubsub/redis_pubsub.go | ✅ Watermill实现 |
|
||||
| EventBus | internal/pubsub/event_bus.go | ✅ topic常量定义 |
|
||||
| Dispatcher | internal/channel/dispatcher.go | ⚠️ 注册机制有,无Listener |
|
||||
| dispatch.Dispatcher | internal/dispatch/dispatcher.go | ⚠️ Sync/Async骨架,无业务接入 |
|
||||
| WorkerPool | internal/worker/worker.go | ❌ 全stub |
|
||||
| ChannelRegistry | internal/channel/registry.go | ✅ 完整实现 |
|
||||
| ChannelProvider | internal/channel/provider.go | ✅ 接口完整 |
|
||||
| WebWidget Provider | internal/channel/web_widget/ | ✅ 完整实现 |
|
||||
| Telegram Provider | internal/channel/telegram/ | ✅ 完整实现 |
|
||||
| Facebook Provider | internal/channel/provider/facebook.go | ❌ 缺失 |
|
||||
| WhatsApp Provider | internal/channel/provider/whatsapp.go | ❌ 缺失 |
|
||||
| InboxHandler | internal/handler/api/v1/inbox_handler.go | ❌ 全stub |
|
||||
| ContactHandler | internal/handler/api/v1/contact_handler.go | ❌ 全stub |
|
||||
| ConversationHandler | internal/handler/api/v1/conversation_handler.go | ❌ 全stub |
|
||||
| MessageHandler | internal/handler/api/v1/message_handler.go | ❌ 全stub |
|
||||
@@ -0,0 +1,280 @@
|
||||
# P10 (M10) Captain AI + Copilot — Chatwoot vs GoChat Comparison
|
||||
|
||||
This document maps every Chatwoot Enterprise Captain/Copilot feature to its GoChat
|
||||
port, highlighting structural differences, naming changes, and intentional deviations.
|
||||
|
||||
---
|
||||
|
||||
## 1. Architecture Overview
|
||||
|
||||
| Aspect | Chatwoot (Ruby/Rails) | GoChat (Go/Gin) |
|
||||
|--------|----------------------|------------------|
|
||||
| Language | Ruby on Rails | Go + Gin framework |
|
||||
| ORM | ActiveRecord | GORM |
|
||||
| Vector DB | pgvector via ActiveRecord | pgvector via pgvector-go |
|
||||
| HTTP client | Net::HTTP / HTTParty | resty/v2 |
|
||||
| DI pattern | Rails autoloading | Manual constructor injection (repos→services→handlers) |
|
||||
| Routing | config/routes.rb (327+ routes) | internal/router/router.go (group-based) |
|
||||
| Config | ENV vars + features.yml | CaptainConfig in config.yaml (viper) |
|
||||
|
||||
---
|
||||
|
||||
## 2. Models
|
||||
|
||||
### Captain::Assistant → CaptainAssistant
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` (bigint) | `ID` (uint, GORM auto) | Same semantics |
|
||||
| `account_id` | `AccountID` (uint) | FK to accounts |
|
||||
| `name` (string) | `Name` (string) | Identical |
|
||||
| `status` (enum: active/draft/archived) | `Status` (AssistantStatus string enum) | Same values, Go uses typed string |
|
||||
| `config` (jsonb) | `Config` (json.RawMessage) | GoChat uses json.RawMessage for lazy parsing; Chatwoot stores as native jsonb |
|
||||
| `created_at/updated_at` | `CreatedAt/UpdatedAt` | GORM auto-managed |
|
||||
| — | `DeletedAt` (gorm.DeletedAt) | GoChat uses soft delete; Chatwoot has no soft delete on this model |
|
||||
|
||||
### Captain::Document → CaptainDocument
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `assistant_id` | `AssistantID` (uint) | FK to captain_assistant |
|
||||
| `account_id` | `AccountID` (uint) | Account scope |
|
||||
| `url` (string) | `URL` (string) | External URL |
|
||||
| `external_url` | `ExternalURL` (string) | Chatwoot distinguishes url vs external_url |
|
||||
| `status` (enum: active/processing/failed) | `Status` (DocumentStatus string enum) | Same values |
|
||||
| `content` (text) | `Content` (string) | Raw document text |
|
||||
| `embedding` (vector(1536)) | `Embedding` (pgvector.Vector) | Both use pgvector 1536-dim cosine |
|
||||
| — | `DeletedAt` | GoChat soft delete |
|
||||
|
||||
### Captain::Scenario → CaptainScenario
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `assistant_id` | `AssistantID` (uint) | FK |
|
||||
| `account_id` | `AccountID` (uint) | Account scope |
|
||||
| `name` | `Name` (string) | Identical |
|
||||
| `description` | `Description` (string) | Identical |
|
||||
| `scenario_type` (enum: greeting/farewell/faq/custom) | `ScenarioType` (ScenarioType string enum) | Same values |
|
||||
| `trigger_keywords` (jsonb) | `TriggerKeywords` (json.RawMessage) | GoChat uses json.RawMessage |
|
||||
| `response_template` (text) | `ResponseTemplate` (string) | Identical |
|
||||
| — | `DeletedAt` | Soft delete |
|
||||
|
||||
### Captain::InboxAssistant → CaptainInbox
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `inbox_id` | `InboxID` (uint) | FK to inbox |
|
||||
| `assistant_id` | `AssistantID` (uint) | FK to captain_assistant |
|
||||
| `account_id` | `AccountID` (uint) | Account scope |
|
||||
|
||||
### Captain::Response → CaptainResponse
|
||||
|
||||
Chatwoot model is a join/pivot; GoChat keeps it as a standalone model for audit trail.
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `assistant_id` | `AssistantID` (uint) | FK |
|
||||
| `conversation_id` | `ConversationID` (uint) | FK |
|
||||
| `account_id` | `AccountID` (uint) | Account scope |
|
||||
| `content` (text) | `Content` (string) | The AI-generated response |
|
||||
| `source` (enum) | `Source` (string) | "captain" / "copilot" |
|
||||
|
||||
---
|
||||
|
||||
## 3. Copilot Models
|
||||
|
||||
### Captain::CopilotThread → CopilotThread
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `account_id` | `AccountID` (uint) | Account scope |
|
||||
| `user_id` | `UserID` (uint) | Who owns this thread |
|
||||
| `title` | `Title` (string) | Thread display name |
|
||||
| `status` | `Status` (string) | active/archived |
|
||||
| `metadata` (jsonb) | `Metadata` (json.RawMessage) | Flexible metadata |
|
||||
| — | `DeletedAt` | Soft delete |
|
||||
|
||||
### Captain::CopilotMessage → CopilotMessage
|
||||
|
||||
| Chatwoot Field | GoChat Field | Notes |
|
||||
|----------------|-------------|-------|
|
||||
| `id` | `ID` (uint) | Same |
|
||||
| `thread_id` | `ThreadID` (uint) | FK to copilot_thread |
|
||||
| `role` (enum: user/assistant/system) | `Role` (string) | Same values |
|
||||
| `content` (text) | `Content` (string) | Message body |
|
||||
|
||||
---
|
||||
|
||||
## 4. LLM Provider Abstraction
|
||||
|
||||
| Chatwoot | GoChat | Notes |
|
||||
|----------|--------|-------|
|
||||
| `Captain::BaseAiService` | `llm.LLMProvider` (interface) | GoChat uses Go interface; Chatwoot uses Ruby class hierarchy |
|
||||
| `Captain::OpenAiService` | `llm.OpenAIProvider` (struct) | Both implement OpenAI chat+embeddings |
|
||||
| — | `llm.ProviderConfig` | Config struct mapped from CaptainConfig in config.yaml |
|
||||
| — | `llm.ChatRequest/ChatResponse` | Unified request/response types |
|
||||
| — | `llm.EmbeddingRequest/EmbeddingResponse` | Embedding types |
|
||||
| — | `llm.StreamChunk` | Streaming support (Chatwoot uses SSE via ActionController::Live) |
|
||||
| — | `llm.ToolCall/ToolResult` | Structured tool call interface |
|
||||
|
||||
### Key Differences
|
||||
- **Chatwoot**: Uses `Net::HTTP` with custom streaming; GoChat uses `resty.Client`
|
||||
- **Chatwoot**: Environment variables for API keys; GoChat uses `CaptainConfig` struct via viper
|
||||
- **Chatwoot**: Provider selection via feature flags; GoChat uses `captain.llm_provider` config field
|
||||
|
||||
---
|
||||
|
||||
## 5. Tool Registry
|
||||
|
||||
| Chatwoot | GoChat | Notes |
|
||||
|----------|--------|-------|
|
||||
| `Captain::ToolRegistry` (Ruby module) | `llm.ToolRegistry` (Go struct) | Both register named tools |
|
||||
| `Captain::SearchDocumentationService` | `llm.SearchDocumentationService` | Vector similarity search as a tool |
|
||||
| `Captain::CustomHttpTool` | `llm.CustomHttpTool` | HTTP call tool with headers/body config |
|
||||
| — | `llm.BaseTool` (interface) | Go interface for extensible tools; Chatwoot uses module mixins |
|
||||
|
||||
---
|
||||
|
||||
## 6. Services
|
||||
|
||||
| Chatwoot Service | GoChat Service | Notes |
|
||||
|-------------------|---------------|-------|
|
||||
| `Captain::AssistantService` | `CaptainAssistantService` | CRUD + config update + list by account |
|
||||
| `Captain::DocumentService` | `CaptainDocumentService` | CRUD + search similar (vector) + list by assistant |
|
||||
| `Captain::ScenarioService` | `CaptainScenarioService` | CRUD |
|
||||
| `Captain::InboxAssistantService` | (embedded in CaptainAssistantService.BindInbox/UnbindInbox) | GoChat folds inbox binding into assistant service |
|
||||
| `Captain::CopilotService` | `CopilotService` | Thread CRUD + message CRUD + LLM calls |
|
||||
| — | `ReplySuggestionService` | Extracted as sub-service within CopilotService |
|
||||
| — | `ConversationSummaryService` | Extracted as sub-service within CopilotService |
|
||||
| — | `TranslationService` | Extracted as sub-service within CopilotService |
|
||||
|
||||
### Key Differences
|
||||
- **Chatwoot**: Inbox binding is a separate service; GoChat folds it into CaptainAssistantService
|
||||
- **Chatwoot**: Copilot features are separate controller actions; GoChat uses a single CopilotService with sub-methods
|
||||
- **GoChat**: Each service takes its repo(s) via constructor injection (newer pattern)
|
||||
|
||||
---
|
||||
|
||||
## 7. Repositories
|
||||
|
||||
| Chatwoot | GoChat | Notes |
|
||||
|----------|--------|-------|
|
||||
| ActiveRecord scopes (`.where`, `.order`) | GORM `.Where`, `.Order`, `.Limit` | Equivalent query patterns |
|
||||
| `Captain::Assistant.where(account_id: id)` | `CaptainAssistantRepo.ListByAccount(accountID)` | Named methods |
|
||||
| Vector search: raw SQL `ORDER BY embedding <=> $1` | `CaptainDocumentRepo.SearchSimilar(embedding, limit)` using pgvector-go cosine distance | Both use pgvector cosine distance operator |
|
||||
| — | `BaseRepository[T]` generic | GoChat has a generic base repo pattern |
|
||||
|
||||
---
|
||||
|
||||
## 8. Handlers / Controllers
|
||||
|
||||
| Chatwoot Controller | GoChat Handler | Routes |
|
||||
|---------------------|---------------|--------|
|
||||
| `Captain::AssistantsController` | `CaptainAssistantHandler` | `/accounts/:id/captain/assistants` |
|
||||
| `Captain::DocumentsController` | `CaptainDocumentHandler` | `/accounts/:id/captain/assistants/:aid/documents` |
|
||||
| `Captain::ScenariosController` | `CaptainScenarioHandler` | `/accounts/:id/captain/assistants/:aid/scenarios` |
|
||||
| `Captain::CustomToolsController` | `CaptainCustomToolHandler` | `/accounts/:id/captain/custom_tools` |
|
||||
| `Captain::CopilotThreadsController` | `CopilotHandler.ListThreads/CreateThread/GetThread/DeleteThread` | `/accounts/:id/captain/copilot_threads` |
|
||||
| `Captain::CopilotMessagesController` | `CopilotHandler.SendMessage` | `/accounts/:id/captain/copilot_threads/:id/messages` |
|
||||
| — | `CopilotHandler.SuggestReplies` | `/accounts/:id/captain/copilot/suggest_replies` |
|
||||
| — | `CopilotHandler.SummarizeConversation` | `/accounts/:id/captain/copilot/summarize` |
|
||||
| — | `CopilotHandler.TranslateMessage` | `/accounts/:id/captain/copilot/translate` |
|
||||
|
||||
### Key Differences
|
||||
- **Chatwoot**: Separate controllers per resource; GoChat uses grouped handlers with method-per-action
|
||||
- **Chatwoot**: Copilot threads/messages are separate controllers; GoChat unifies under single CopilotHandler
|
||||
- **GoChat**: Uses `response.Success/Error/JSON` helpers; Chatwoot uses Rails `render json:`
|
||||
- **GoChat**: Uses `parseUintParam/getAccountID/getUserID` helpers for param extraction
|
||||
|
||||
---
|
||||
|
||||
## 9. Route Structure
|
||||
|
||||
| Chatwoot Route Pattern | GoChat Route Pattern | Notes |
|
||||
|------------------------|---------------------|-------|
|
||||
| `namespace :api, scope :v1` → `namespace :accounts` → `namespace :captain` | `/api/v1/accounts/:account_id/captain/...` | Same nesting structure |
|
||||
| `resources :assistants` | `captain.Group("/assistants")` | Identical CRUD |
|
||||
| `assistants/:id/inboxes` (POST/DELETE) | `assistants.POST("/:id/inboxes")` | Inbox binding routes |
|
||||
| `assistants/:assistant_id/documents` | `assistants.Group("/:assistant_id/documents")` | Nested documents |
|
||||
| `assistants/:assistant_id/scenarios` | `assistants.Group("/:assistant_id/scenarios")` | Nested scenarios |
|
||||
| `resources :custom_tools` | `captain.Group("/custom_tools")` | Account-level tools |
|
||||
| `resources :copilot_threads` | `captain.Group("/copilot_threads")` | Thread CRUD |
|
||||
| `copilot/suggest_replies` | `captain.POST("/copilot/suggest_replies")` | AI action endpoints |
|
||||
| `copilot/summarize` | `captain.POST("/copilot/summarize")` | Summary endpoint |
|
||||
| `copilot/translate` | `captain.POST("/copilot/translate")` | Translation endpoint |
|
||||
|
||||
---
|
||||
|
||||
## 10. Bootstrap / Dependency Wiring
|
||||
|
||||
| Chatwoot | GoChat | Notes |
|
||||
|----------|--------|-------|
|
||||
| Rails autoloading (no explicit DI) | Manual constructor chain in `bootstrap.go` | Explicit wiring: repos→services→handlers |
|
||||
| `config/initializers/` auto-run | `Bootstrap()` function step-by-step | Ordered dependency creation |
|
||||
| Feature flags (`CAPTAIN_ENABLED`) | `captain.enabled` in config.yaml | Toggle feature availability |
|
||||
| ENV: `OPENAI_API_KEY` | `captain.llm_api_key` | Same concept, different naming |
|
||||
|
||||
---
|
||||
|
||||
## 11. File Inventory
|
||||
|
||||
### GoChat Files Created (P10 M10)
|
||||
|
||||
| File | Lines | Purpose |
|
||||
|------|-------|---------|
|
||||
| `internal/model/captain_models.go` | 237 | 6 Captain models + enums |
|
||||
| `internal/model/copilot_models.go` | ~70 | CopilotThread + CopilotMessage |
|
||||
| `internal/repository/captain_repo.go` | 295 | 6 repos with vector search |
|
||||
| `internal/repository/copilot_repo.go` | 84 | 2 repos |
|
||||
| `internal/service/captain_service.go` | 225 | 4 Captain services |
|
||||
| `internal/service/copilot_service.go` | 238 | CopilotService (CRUD + AI actions) |
|
||||
| `internal/service/llm/provider.go` | 322 | LLMProvider interface + OpenAIProvider |
|
||||
| `internal/service/llm/prompts.go` | 159 | System prompt builders |
|
||||
| `internal/service/llm/tools.go` | ~300 | ToolRegistry + tools |
|
||||
| `internal/handler/api/v1/captain_handler.go` | 624 | 4 Captain handlers |
|
||||
| `internal/handler/api/v1/copilot_handler.go` | 218 | CopilotHandler |
|
||||
| `internal/handler/api/v1/helpers.go` | 69 | parseUintParam, getUserID, getPaginationParams |
|
||||
| `internal/config/config.go` | +17 | CaptainConfig struct |
|
||||
| `internal/router/router.go` | +63 | Captain route group |
|
||||
| `internal/app/bootstrap.go` | +30 | Captain repos, services, LLM, handlers wiring |
|
||||
|
||||
### Modified Existing Files
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `internal/config/config.go` | Added `Captain CaptainConfig` to Config struct |
|
||||
| `internal/router/router.go` | Added 5 handler fields to Handlers struct; Captain route group |
|
||||
| `internal/app/bootstrap.go` | Added llm import; 8 Captain repos; 4 services + LLM provider + copilot; 5 handler wires |
|
||||
|
||||
---
|
||||
|
||||
## 12. Intentional Deviations from Chatwoot
|
||||
|
||||
1. **Soft delete on all models** — GoChat uses `gorm.DeletedAt` universally; Chatwoot Captain models don't soft-delete
|
||||
2. **json.RawMessage instead of native jsonb** — GoChat avoids the GORM datatypes.JSON dependency; lazy parsing in Go
|
||||
3. **Inbox binding folded into assistant service** — GoChat merges inbox binding/unbinding into CaptainAssistantService rather than a separate service
|
||||
4. **Single CopilotHandler for all copilot actions** — GoChat unifies threads, messages, suggest/summarize/translate under one handler
|
||||
5. **LLMProvider as Go interface** — Chatwoot uses Ruby class hierarchy; GoChat uses interface for polymorphic provider support
|
||||
6. **Config struct for Captain settings** — Chatwoot uses ENV vars; GoChat uses typed CaptainConfig in YAML
|
||||
7. **Separate CopilotService** — GoChat extracts Copilot into its own service rather than nesting under Captain::AssistantService
|
||||
8. **No Captain::ResponseService** — GoChat has the model but no dedicated response service (responses are embedded in assistant service flow)
|
||||
|
||||
---
|
||||
|
||||
## 13. Missing / Deferred Items
|
||||
|
||||
| Feature | Status | Notes |
|
||||
|---------|--------|-------|
|
||||
| Document processing (ingestion, chunking) | Deferred | Needs document processor service |
|
||||
| Embedding generation pipeline | Deferred | Needs background job to call OpenAI embeddings API |
|
||||
| Streaming responses (SSE) | Stub only | Chatwoot uses ActionController::Live; GoChat needs Gin SSE implementation |
|
||||
| Captain webhook for inbox events | Deferred | Needs P7 channel integration |
|
||||
| Response quality scoring | Deferred | Chatwoot has response rating; GoChat model has fields but no scoring service |
|
||||
| Multiple LLM providers (Azure, Anthropic, Ollama) | Interface ready | OpenAIProvider implemented; others stubs |
|
||||
| Admin UI for Captain config | Deferred | Front-end work |
|
||||
| Document upload endpoint | Missing | Chatwoot has file upload; GoChat needs multipart handler |
|
||||
Reference in New Issue
Block a user