second commit

This commit is contained in:
Rogee
2026-06-04 15:44:48 +08:00
parent 4db6efb3a7
commit 8ac150bc7b
1275 changed files with 286124 additions and 0 deletions
@@ -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 |