# GoChat 消息生命周期与 AI 自动回复流程 > 本文档描述从渠道消息入站、人工/AI 回复、消息打标签到 AI 自动触发的完整运行流程。 > 所有文件路径相对于仓库根目录,行号截至 2026-07-30。 --- ## 一、整体架构概览 ``` ┌─────────────────────────────────────────────────────────────────────┐ │ 外部渠道 │ │ Facebook │ Instagram │ TikTok │ Telegram │ LINE │ Email │ Web Widget │ API └─────┬──────────┬──────────┬──────────┬─────────┬───────┬──────┬──────┘ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Webhook 路由层 (router.go) │ │ /webhooks/facebook /webhooks/instagram /webhooks/tiktok ... │ └──────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Channel Provider 层 (插件接口) │ │ FacebookProvider InstagramProvider TikTokProvider TelegramProvider│ │ IncomingMessage() 解析渠道原始 payload → channel.IncomingMessage │ └──────────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ IncomingPersister (持久化层) │ │ 1. resolveOrCreateContactInbox — 查找/创建 Contact + ContactInbox │ │ 2. resolveOrCreateConversation — 查找/创建 Conversation │ │ 3. createMessage — 持久化 Message │ │ 4. dispatchIncomingEvents — 广播 channel events │ └──────────────────────────────┬──────────────────────────────────────┘ │ DispatchAsync ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ Dispatcher (事件分发总线) │ │ channel.Dispatcher → 广播 ChannelEvent 给所有注册的 EventListener │ │ │ │ 注册的 Listener: │ │ ├── AutoReplyListener — AI 自动回复 │ │ ├── AutomationRuleListener— 自动化规则(标签/分配/状态变更) │ │ ├── ActionCableListener — WebSocket 推送到前端 │ │ ├── NotificationListener — 创建通知 │ │ ├── AgentBotRuleListener — Agent Bot 规则 │ │ └── AutoAssignmentListener— 自动分配 │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 二、入站消息流(Inbound) ### 2.1 Webhook 路由注册 路由在 `backend/internal/router/router.go` 中注册: - **Facebook**: `POST /webhooks/facebook` → `FacebookWebhookHandler.HandleWebhook` - **Instagram**: `POST /webhooks/instagram` → `InstagramWebhookHandler` - **TikTok**: `POST /webhooks/tiktok` → `TikTokWebhookHandler` - **Telegram**: `POST /webhooks/telegram/:inbox_id` → `TelegramWebhookHandler` - **LINE**: `POST /webhooks/line/:inbox_id` → `LineWebhookHandler` - **Email**: IMAP 轮询 + ActionMailbox 端点 - **Web Widget**: WebSocket 实时消息(不走 webhook) - **API Channel**: `POST /webhooks/api/:inbox_id` → 外部系统主动推送 ### 2.2 Webhook Handler → Provider → IncomingMessage 以 Facebook 为例: ``` FacebookWebhookHandler.HandleWebhook (handler/webhook/facebook_webhook.go) → 解析 Facebook webhook payload (entry[].messaging[]) → 遍历每条 messaging event → 调用 FacebookProvider.IncomingMessage() (channel/facebook/provider.go) → 解析 sender_id, recipient_id, message text, attachments → 返回 channel.IncomingMessage{ SourceID: "facebook_msg_xxx", ConversationID: senderPSID, Content: messageText, ContentType: channel.ContentText, Attachments: []Attachment{...}, ChannelType: channel.ChannelFacebook, } ``` 其他渠道同理,每个 Provider 实现 `IncomingMessage()` 方法将渠道原始数据归一化为 `channel.IncomingMessage` 结构。 ### 2.3 IncomingPersister 持久化 `backend/internal/handler/webhook/incoming_persister.go:74` ``` IncomingPersister.PersistIncoming(ctx, inbox, msg) │ ├── 若有 WorkerPool → enqueueIncomingMessagePersist (异步队列) │ └── providerIncomingMessagePersistJob (incoming_persister_jobs.go:24) │ └── performPersistIncoming (异步执行) │ └── performPersistIncoming(ctx, inbox, msg) — 同步路径 │ ├── 1. resolveOrCreateContactInbox (L451) │ ├── 按 inbox_id + source_id 查找 ContactInbox │ ├── 找到 → 返回关联的 Contact(更新 name 等) │ └── 未找到 → 创建 Contact + ContactInbox │ ├── 2. resolveOrCreateConversation (L504) │ ├── 查找 account_id+inbox_id+contact_id+status=open 的最近会话 │ ├── 找到 → 更新 last_activity_at, last_message_at │ └── 未找到 → 创建新 Conversation (status=open) │ ├── 3. createMessage (L545) │ ├── 构建 model.Message{SenderType:"Contact", MessageType:"incoming"} │ ├── 处理 in_reply_to(按 source_id 查找被回复的消息) │ ├── 处理 attachments │ └── messageRepo.Create() 持久化到 messages 表 │ └── 4. dispatchIncomingEvents (L383) ├── EventContactCreated (仅新联系人) ├── EventConversationCreated + EventConversationOpened (仅新会话) ├── EventConversationUpdated (已有会话) ├── EventMessageCreated └── EventMessageIncoming └── dispatcher.DispatchAsync() → 广播给所有 Listener ``` ### 2.4 WebSocket 实时推送 ``` ActionCableListener.OnEvent (internal/wsevent/bridge_listener.go:51) ├── 接收 EventMessageCreated / EventMessageIncoming ├── 构建 WebSocket payload (message JSON + conversation meta) └── wsHub.Broadcast() → 推送到该 account 所有在线 agent 前端 └── 前端 Vue SPA WebSocket 连接接收 → Vuex/Pinia store 更新 → UI 刷新 ``` #### Durable realtime 投递语义 配置 WorkerPool 后,`EventPublisher` 先把 account 与 pubsub token 目标分别写入 `background_jobs`。在当前 Redis relay 配置下,account target job 发布到 account room,由 `BroadcastRelay` 转发到 Hub,并由执行 job 的实例写 account SSE;pubsub token target job 只发布到 token room,经 relay 转发到 Hub,不写 SSE。并非每个 job 都同时直发 Redis、Hub 和 SSE。 幂等键避免同一条 `message.created`/目标重复入队,job claim 避免并发执行同一条 记录;它们不覆盖发布副作用与 job 完成状态之间的崩溃窗口: 1. job 已完成 Redis 发布,并可能已完成本地 SSE 投递; 2. 进程在将 `background_jobs.status` 更新为 `completed` 前退出; 3. stale-job 恢复将该记录重新置为可执行并再次发布。 因此,对外契约是 **durable at-least-once publication**,不是 exactly-once delivery。进程恢复后,在线 WebSocket/SSE 消费者可能再次收到同一事件;account 与 pubsub token 是独立 job,重试和到达顺序也彼此独立。Redis Pub/Sub、Hub 和 SSE 不保存离线或慢消费者的确认状态,所以该契约保证发布尝试可恢复,不保证每个 客户端至少接收一次。 该保证从 job 成功写入 `background_jobs` 开始。事务内入队失败会使业务事务回滚; 非事务入队失败则没有 durable job,因而不保证该事件会被发布。每个 target job 的 `MaxAttempts=3`;达到上限(或遇到 permanent error)进入 `dead` 后不会再被自动 重试,也不再保证发布,消费者仍需通过 REST reconciliation 恢复持久化状态。 消费者必须把 realtime 事件作为可重放通知处理: - `message.created` 使用订阅作用域、事件名和 payload 的稳定 message `id` 去重或 upsert;不得使用未出现在 wire payload 中的 background job ID。 - `*.updated` 按资源 `id` 幂等 upsert,并在 payload 提供 `updated_at`/版本时拒绝 旧更新;不要仅按资源 ID 永久丢弃后续合法更新。 - 重连或发现序列缺口时,以 REST API 返回的持久化资源为准。会触发声音、通知、 SDK callback 或其他非幂等副作用的外部消费者必须在执行副作用前自行去重。 当前风险可接受:复用的 dashboard 会按 message ID 替换重复消息,并按 conversation `updated_at` 忽略旧更新;widget 消息仓库也按 message ID upsert。 这与上游 Chatwoot 的异步 `ActionCableBroadcastJob` 行为一致。只有当业务要求 跨进程崩溃的 exactly-once 副作用时,才应另行引入稳定 transport event ID 与 消费端 inbox/ack;当前协议不承诺该能力。 ### 2.5 通知创建 ``` NotificationListener.OnEvent ├── 接收 EventMessageIncoming ├── 检查 inbox 的通知设置 ├── 创建 Notification 记录 (notification 表) ├── 推送 Push notification (PushDeliveryService) │ └── SendPushNotification (push_delivery_service.go:63) └── 发送邮件通知(如配置) ``` --- ## 三、出站回复流(Outbound) ### 3.1 前端发起 ``` Agent 在 ReplyBox 输入消息 → 点击发送 │ ├── frontend/app/javascript/dashboard/api/inbox/message.js:56 │ MessageApi.create({ conversationId, message, ... }) │ → POST /api/v1/accounts/:id/conversations/:conversation_id/messages │ └── 前端 Vuex store: conversations/actions.js createPendingMessageAndSend → MessageApi.create() ``` ### 3.2 后端处理 ``` MessageHandler.Create (handler/api/v1/message_handler.go) → 解析请求 body (content, private, files, echo_id, ...) → 调用 MessageService.Create() │ │ backend/internal/service/message_service.go │ ├── 构建 model.Message{MessageType:"outgoing", SenderType:"User"} ├── messageRepo.Create() — 持久化消息 ├── 更新 conversation.last_activity_at, last_message_at ├── dispatchMessageEvent(EventMessageCreated + EventMessageOutgoing) │ └── dispatcher.Dispatch() → 广播给 Listener │ ├── ActionCableListener → WebSocket 推送 (agent 端即时看到) │ ├── AutomationRuleListener → 触发自动化规则 │ └── 其他 Listener │ └── 投递到渠道 (MessageDeliveryWorker) │ ├── 若有 WorkerPool → EnqueueSendReply (异步队列) │ └── message_delivery_worker.go: CreateOutgoing → SendReply │ └── SendReply(messageID) ├── 加载 message + conversation + inbox + contact ├── 根据 inbox.ChannelType 获取对应 Provider ├── 调用 Provider.SendMessage(ctx, inbox, message, contact) │ ├── Facebook: POST graph.facebook.com/me/messages │ ├── Instagram: POST graph.facebook.com/me/messages (IGID) │ ├── TikTok: POST business-api.tiktok.com/... │ ├── Telegram: POST api.telegram.org/bot.../sendMessage │ ├── Email: SMTP 发送 │ ├── Web Widget: WebSocket 推送到 widget 端 │ └── API: POST 到 inbox.webhook_url ├── 更新 message.Status = "sent" / "delivered" / "failed" └── dispatchMessageEvent(EventMessageStatusUpdated) ``` ### 3.3 消息状态回执 渠道 webhook 回调消息已读/已送达状态: ``` FacebookWebhookHandler → 解析 delivery/read event → IncomingPersister.dispatchMessageStatusEvent() → 更新 message.Status → dispatcher.Dispatch(EventMessageStatusUpdated) → ActionCableListener → WebSocket 推送状态更新到前端 ``` --- ## 四、标签系统 ### 4.1 数据模型(双路径并存) GoChat 标签系统存在两条并行路径: **路径 A — Tag 关联表(结构化)** - **Tag** (`backend/internal/model/tag.go:12`): 表名 `tags`,字段 `ID, AccountID, Name(账号内唯一), Color, Description, ShowOnSidebar` - 唯一索引:`idx_tag_account_name`(AccountID + Name) - **ConversationLabel** (`backend/internal/model/conversation_label.go:11`): 表名 `conversation_labels`,字段 `ID, ConversationID, TagID, AccountID` - 唯一索引:`idx_conv_label_tag`(ConversationID + TagID),防止重复关联 - Repository: `TagRepo` (`repository/tag_repo.go`) — `DeleteWithAssociations` 删除 Tag 时同步清理关联行和 `conversations.labels` 文本字段 **路径 B — Conversation.Labels 文本字段(遗留)** - `Conversation.Labels string` (`model/conversation.go:30`) — 逗号分隔字符串,如 `"billing,urgent"` - 辅助函数: `mergeConversationLabels()` / `splitConversationLabels()` (`conversation_maintenance_worker.go:726,755`) - `TagRepo.RenameConversationLabelText` (`repository/tag_repo.go:70`) — Tag 重命名时同步更新 `conversations.labels` 中的文本 两条路径在自动化规则和手动操作中同时维护。 ### 4.2 标签 CRUD API ``` LabelHandler (handler/api/v1/label_handler.go) ├── GET /api/v1/accounts/:id/labels — 列出标签 ├── POST /api/v1/accounts/:id/labels — 创建标签 ├── PATCH /api/v1/accounts/:id/labels/:id — 更新标签 └── DELETE /api/v1/accounts/:id/labels/:id — 删除标签 │ └── LabelService (service/label_service.go) └── LabelRepo (repository/label_repo.go) ``` ### 4.3 会话标签操作 ``` ConversationHandler (handler/api/v1/conversation_handler.go) ├── POST /conversations/:id/labels — 给会话打标签 └── POST /conversations/:id/labels — 移除标签 │ └── ConversationService.ToggleLabels() ├── 更新 conversation.labels 字段 (mergeConversationLabels) ├── dispatchConversationEventWithData(EventConversationLabelsUpdated) └── ActionCableListener → WebSocket 推送标签变更到前端 ``` ### 4.4 自动化规则中的标签 ``` AutomationRule (model/automation_rule.go) └── AutomationAction.ActionType = "add_label" └── ActionParams = {"labels": ["billing", "urgent"]} AutomationRuleListener.OnEvent (automation/listener.go) ├── 接收 EventMessageCreated / EventConversationCreated ├── ConditionsFilterService 检查规则条件是否匹配 ├── 匹配 → ActionService 执行动作 │ └── add_label → 更新 conversation.labels └── dispatch EventConversationLabelsUpdated ``` ### 4.5 前端标签管理 ``` LabelBox.vue (dashboard/routes/dashboard/conversation/labels/LabelBox.vue) ├── 显示当前会话所有标签 ├── 添加/移除标签 → API 调用 └── useConversationLabels composable ``` --- ## 五、AI 自动回复 ### 5.1 架构概览 ``` 入站消息 EventMessageCreated │ ▼ ┌────────────────────────────────┐ │ AutoReplyListener │ │ (service/auto_reply_listener.go)│ └───────────────┬────────────────┘ │ ┌─────────▼──────────┐ │ EvaluateRules() │ │ (auto_reply_rule │ │ _service.go) │ └─────────┬──────────┘ │ ┌───────────────┼───────────────┐ │ │ │ ▼ ▼ ▼ Static Mode LLM Mode Mixed Mode (固定文本回复) (LLM 生成回复) (LLM + 固定文本) │ │ │ │ ┌──────▼──────┐ │ │ │ LLM Provider │ │ │ │ (RAG 知识库) │ │ │ └──────┬──────┘ │ │ │ │ └───────────────┼───────────────┘ ▼ MessageService.Create() (outgoing message) │ ▼ MessageDeliveryWorker → 渠道发送 ``` ### 5.2 AutoReplyListener 触发链路 `backend/internal/service/auto_reply_listener.go` ``` AutoReplyListener.OnEvent(ctx, event) (L62) │ ├── 过滤:仅处理 EventMessageCreated / EventMessageIncoming ├── 过滤:仅处理 sender_type = "Contact" 的入站消息 ├── 加载 Conversation(检查是否已 resolved → 跳过) ├── fetchRecentMessages — 取最近 10 条消息作为上下文 (L205) │ ├── 构建 AutoReplyEvaluationContext { │ AccountID, InboxID, ConversationID, │ MessageContent, PreviousMessages, │ ConversationStatus, Language │ } │ ├── AutoReplyRuleService.EvaluateRules(ctx, evalCtx) (auto_reply_rule_service.go) │ ├── 按 inbox_id 查找 active 状态的 auto-reply rules │ ├── 逐条评估 conditions(关键词匹配 / 正则 / 消息长度 / 时间窗口) │ ├── 按 priority 排序,返回第一个匹配的 rule │ └── 根据 rule.Mode 生成回复内容: │ ├── Static: 直接返回 rule.ResponseText │ ├── LLM: 调用 LLM Provider 生成回复(可带 RAG 检索) │ └── Mixed: LLM 生成 + 固定文本拼接 │ ├── 检查 OneTimeOnly — 该规则是否已对此会话触发过 (L225) │ ├── 若有 DelaySeconds → 异步 goroutine 延迟发送 (L137) │ └── sendAutoReply(ctx, event, conversation, result) (L151) ├── 解析 inbox 关联的 AgentBot 作为 sender ├── 构建 CreateMessageRequest{ │ Content: replyContent, │ MessageType: "outgoing", │ SenderType: "agent_bot", │ } └── MessageService.Create() → 消息持久化 + 事件分发 └── → MessageDeliveryWorker → 渠道发送 ``` ### 5.3 Auto-Reply Rule 数据模型 `backend/internal/model/captain_auto_reply_rule.go` ``` CaptainAutoReplyRule { AccountID AssistantID — 关联 Captain Assistant InboxID — 绑定到特定 inbox Name, Description Status — draft / active / archived Mode — static / llm / mixed Priority — 数字越小优先级越高 Conditions — JSON: 关键词/正则/消息属性条件 ResponseText — static/mixed 模式的固定回复文本 LLMPromptOverride — LLM 模式的自定义 prompt DelaySeconds — 延迟发送秒数 OneTimeOnly — 是否只触发一次 } ``` ### 5.4 Captain Assistant Captain 是 GoChat 的 AI 助手框架,提供 LLM 能力: ``` CaptainAssistant (model/captain_assistant.go) ├── 关联一个 LLM Provider (OpenAI / DeepSeek / 自定义) ├── 关联 RAG 文档库 (CaptainDocument) ├── 关联 Scenario(场景化 prompt 模板) └── 关联 Auto-Reply Rules CaptainAssistantHandler (handler/api/v1/captain_assistant_handler.go) ├── CRUD assistant ├── 测试 assistant (发送测试消息) └── 生成回复 (直接调用 LLM) CaptainConversationHandler (handler/api/v1/captain_conversation_handler.go) ├── AI 参与会话 — Captain 读取会话上下文 + RAG 检索 → 生成建议回复 └── 也可直接发送 AI 回复到会话 ``` ### 5.5 Copilot(Agent 辅助) Copilot 是给人工 agent 用的 AI 辅助工具,不会自动发送回复: ``` CopilotHandler (handler/api/v1/copilot_handler.go) ├── POST /copilot/suggest — 根据会话上下文生成建议回复 ├── POST /copilot/rephrase — 改写 agent 输入的文本 └── POST /copilot/summarize — 总结会话 CopilotContainer.vue (前端侧边栏) ├── Agent 在侧边栏与 Copilot 交互 ├── Copilot 调用 LLM + RAG 生成建议 └── Agent 确认后手动发送(不自动发送) ``` ### 5.6 RAG 知识检索 ``` RAGHandler (handler/api/v1/rag_handler.go) ├── 上传文档 → 向量化 → 存入 pgvector ├── 检索:query 向量化 → pgvector 相似度搜索 → 返回 top-k 文档片段 └── 在 LLM 调用时注入检索到的上下文 流程: 用户消息 → 向量化 → pgvector 检索相关文档 → 注入 LLM prompt → 生成回复 ``` --- ## 六、完整消息生命周期 ``` 客户 GoChat 后端 Agent 前端 │ │ │ │ 1. 发送消息 │ │ │ ──────────────────────► │ │ │ 2. Webhook 接收 │ │ 3. Provider 解析 │ │ 4. IncomingPersister 持久化 │ │ 5. Dispatcher 广播事件 │ │ │ 6. WebSocket 推送 │ │ │ ────────────────────────────► │ │ │ 7. Agent 看到消息 │ │ │ │ │ 8. AutoReplyListener 检查 │ │ 9. 匹配规则 → AI 生成回复 │ │ 10. MessageService.Create() │ │ 11. MessageDeliveryWorker │ │ 12. 渠道发送回复 │ │ │ ◄────────────────────── │ │ │ │ 13. WebSocket 推送 AI 回复 │ │ │ ────────────────────────────► │ │ │ 14. Agent 看到 AI 回复 │ │ │ │ │ │ 15. (可选) Agent 手动回复 │ │ │ ◄──────────────────────────── │ │ 16. MessageService.Create() │ │ 17. Provider.SendMessage() │ │ 18. 渠道发送回复 │ │ │ ◄────────────────────── │ │ │ │ │ │ │ 19. AutomationRuleListener │ │ │ → 匹配规则 → 自动打标签 │ │ │ → 自动分配 agent │ │ │ → 自动变更状态 │ ``` --- ## 七、关键文件索引 | 组件 | 文件路径 | 关键函数/行号 | |---|---|---| | Webhook 路由 | `backend/internal/router/router.go` | webhook 路由注册 | | Facebook Webhook | `backend/internal/handler/webhook/facebook_webhook.go` | `HandleWebhook` | | TikTok Webhook | `backend/internal/handler/webhook/tiktok_webhook.go` | `HandleWebhook` | | LINE Webhook | `backend/internal/handler/webhook/line_webhook.go` | `HandleWebhook` | | IncomingPersister | `backend/internal/handler/webhook/incoming_persister.go` | `PersistIncoming` L74 | | 持久化 Job | `backend/internal/handler/webhook/incoming_persister_jobs.go` | `providerIncomingMessagePersistJob` L24 | | Dispatcher | `backend/internal/channel/dispatcher.go` | `Dispatch` L85, `DispatchAsync` L110 | | Channel Event | `backend/internal/channel/event.go` | `ChannelEvent` 结构体, EventType 常量 | | Facebook Provider | `backend/internal/channel/facebook/provider.go` | `IncomingMessage`, `SendMessage` L352 | | Instagram Provider | `backend/internal/channel/facebook/instagram_provider.go` | `IncomingMessage`, `SendMessage` L364 | | Message Service | `backend/internal/service/message_service.go` | `Create`, `dispatchMessageEvent` L68 | | Message Handler | `backend/internal/handler/api/v1/message_handler.go` | `Create` | | Message Delivery Worker | `backend/internal/service/message_delivery_worker.go` | `EnqueueSendReply`, `SendReply` | | AutoReply Listener | `backend/internal/service/auto_reply_listener.go` | `OnEvent` L62, `sendAutoReply` L151 | | AutoReply Rule Service | `backend/internal/service/auto_reply_rule_service.go` | `EvaluateRules` | | AutoReply Rule Model | `backend/internal/model/captain_auto_reply_rule.go` | `CaptainAutoReplyRule` | | Captain Assistant Handler | `backend/internal/handler/api/v1/captain_assistant_handler.go` | CRUD + 测试 | | Captain Conversation Handler | `backend/internal/handler/api/v1/captain_conversation_handler.go` | AI 会话参与 | | Copilot Handler | `backend/internal/handler/api/v1/copilot_handler.go` | suggest/rephrase/summarize | | RAG Handler | `backend/internal/handler/api/v1/rag_handler.go` | 文档上传/检索 | | Automation Rule Listener | `backend/internal/automation/listener.go` | `OnEvent` | | Automation Action | `backend/internal/model/automation_action.go` | `AutomationAction` L10 | | Tag Model | `backend/internal/model/tag.go` | `Tag` L12 | | ConversationLabel Model | `backend/internal/model/conversation_label.go` | `ConversationLabel` L11 | | Tag Repo | `backend/internal/repository/tag_repo.go` | `DeleteWithAssociations` L54, `RenameConversationLabelText` L70 | | ConversationLabel Repo | `backend/internal/repository/conversation_label_repo.go` | CRUD | | WebSocket Hub | `backend/internal/ws/hub.go` | `Broadcast` | | WS Event Bridge | `backend/internal/wsevent/bridge_listener.go` | `ActionCableListener.OnEvent` L51 | | Inbox Serializer | `backend/internal/handler/api/v1/inbox_serializer.go` | `serializeInbox` L21 | | Notification Service | `backend/internal/service/notification_delivery_service.go` | 通知投递 | | Push Delivery | `backend/internal/service/push_delivery_service.go` | `SendPushNotification` L63 | | 前端 Message API | `frontend/app/javascript/dashboard/api/inbox/message.js` | `create` L56 | | 前端 ReplyBox | `frontend/app/javascript/dashboard/components/widgets/conversation/ReplyBox.vue` | 消息输入框 | | 前端 Copilot | `frontend/app/javascript/dashboard/components/copilot/CopilotContainer.vue` | Copilot 侧边栏 |