Commit Graph
53 Commits
Author SHA1 Message Date
Rogee dc2a6577bf fix(shangwutong): 来源信息改为系统消息 2026-08-06 17:55:54 +08:00
Rogee d2aeea5f44 fix(shangwutong): 展示会话来源信息 2026-08-06 16:54:57 +08:00
Rogee 5c47d1d5fe fix(shangwutong): 完善访客资料同步 2026-08-06 15:14:32 +08:00
Rogee 8d002d1dbe fix(conversations): 修复会话标识与解决后跳转 2026-08-06 11:19:39 +08:00
Rogee b78f963472 chore: 提交剩余开发改动
包含商务通消息映射、会话时间与未读状态、前端消息展示、数据库修复迁移、测试覆盖备份及 Captain 设计文档。
2026-08-06 10:10:25 +08:00
Rogee 8f2cc12628 fix(conversations): 修复状态切换与历史会话路由
- 处理历史主键与 display_id 冲突,确保消息和状态操作命中同一会话
- 允许已软删除收件箱中的历史会话切换状态
- 解决会话后自动打开列表中的相邻会话
2026-08-06 10:08:05 +08:00
Rogee 3d7ff5a6b1 chore: complete Shangwutong connector integration 2026-08-04 18:00:10 +08:00
Rogee 4611c0201f test: improve backend unit coverage 2026-08-04 12:26:25 +08:00
rogee 897b4e018f feat(channels): add Shangwutong connector 2026-08-03 10:13:40 +08:00
Rogee 96f5c796a6 fix: widget SDK无法加载的三个问题
1. Vite proxy缺少/widget转发 — SDK调用/widget/init等API时被SPA fallback
   拦截返回HTML。添加/widget proxy并bypass HTML页面请求到/widget.html。

2. ParseWebWidgetConfig类型不匹配 — seed数据中channel_config JSON的
   "continuity_via_email":""是空字符串,但Go结构体字段是bool,
   json.Unmarshal报UnmarshalTypeError导致整个config解析失败,
   findInboxByWebsiteToken跳过该inbox。容忍类型不匹配错误,
   只对JSON语法错误返回error。

3. dev模式SDK bundle不可用 — /sdk/js/sdk.js在Vite dev模式下无构建产物,
   被SPA fallback返回HTML。构建SDK后放入public/供dev server静态提供。
2026-08-01 22:59:57 +08:00
Rogee 3a62f01942 fix(ui): 自定义工具表单 overflow-y-scroll→auto + 认证方式NONE翻译改为'无' + Captain文档爬虫/同步后端 2026-08-01 21:25:55 +08:00
Rogee 1446cfe701 feat(channels): 社交渠道 OAuth 改进 + 集成应用重命名 + AI 流程文档
- Facebook/Instagram/TikTok 渠道 OAuth 授权流程改进
- 新增 oauth/credentialstore 包
- 集成应用 OpenAI 重命名为 OpenAI 兼容 (migration 061)
- 消息生命周期与 AI 流程架构文档
- inbox 管理界面 i18n 更新
2026-08-01 19:38:27 +08:00
Rogee 63df5b8b91 fix(captain): 试验场不查 FAQ 知识库 + 添加常见问题报 400
三个问题修复:

1. 添加常见问题报 400 (assistant_id 类型不匹配)
   - CreateResponseDialog.vue: route.params.assistantId 是字符串,
     传给后端 uint 字段导致 JSON 反序列化失败
   - 修复: Number(route.params.assistantId) 转为数字
   - 同时修复 POST /assistant_responses 无尾部斜杠 307 重定向问题

2. 试验场 playground 不做 RAG 检索
   - generatePlaygroundLLMResponse 只构建 system prompt + 对话历史,
     从不查 FAQ 知识库
   - 新增 retrieveFAQContext(): embed 用户问题 → pgvector 搜索 approved
     FAQ → 注入 system prompt
   - 受 feature_faq 配置开关控制

3. FAQ embedding 无法写入 (pgvector 序列化 + 维度问题)
   - pgvector stub 无 driver.Valuer, GORM Save() 报 SQLSTATE 42804
   - 新增 UpdateEmbedding() 用 ?::vector 原始 SQL 绕过
   - SimilaritySearch 排除 embedding 列 + 手动格式化向量字面量
   - embedding 列从 vector(1536) 改为 vector (跟随模型维度)
   - FAQ 创建/更新时自动索引 embedding (SetRAGService 注入)
2026-08-01 19:37:19 +08:00
Rogee 31238476b1 fix(captain): 修复 playground 500 — GetConfig nil pointer panic + 清理重复 buildSystemPrompt
根因:captain_assistants.config 中 temperature 字段为字符串 "0.5",
json.Unmarshal 到 float64 字段失败,GetConfig() 返回 nil,
调用方吞掉错误后 BuildAssistantPrompt 解引用 nil cfg 导致 panic。

修复:
- GetConfig() unmarshal 失败时返回空 &cfg{} 而非 nil,防止 nil dereference
- 删除 captain_assistant_service.go 中重复的 buildSystemPrompt 函数,
  统一使用 SystemPromptBuilder.BuildAssistantPrompt
- generatePlaygroundLLMResponse / GenerateResponse 增加
  ErrProviderNotConfigured 检查,返回 fallback 消息而非 500
2026-08-01 17:21:01 +08:00
Rogee cd82d079e3 fix(ws): 发送 WebSocket 协议级 Ping 控制帧防止 60s 后连接断开
writePump 只发 ActionCable 文本 JSON ping,从不发 websocket.PingMessage,
导致 readPump 的 PongHandler 永远不触发,ReadDeadline(60s) 到期后连接
被强制关闭。前端每 60 秒弹出"离线""重连"通知。

修复:在 ticker 中先发 PingMessage 控制帧(浏览器自动回 Pong 重置
ReadDeadline),再发 ActionCable 文本 ping(保活 ConnectionMonitor)。

同时包含此前会话中的其他变更:
- 移除安全设置(SAML)页面及路由
- 新增超级管理员控制台前端页面
- platform agent_bots 路由使用 Platform* handler
- 超级管理员入口改为 SPA 内路由跳转
2026-07-30 16:55:44 +08:00
Rogee 37e6d77a2d fix: 帮助中心设置页报 Feature 'knowledge_base' is not available
三个问题:

1. router.go: portal 路由检查 FeatureKnowledgeBase("knowledge_base"),
   但账户 feature_flags 中只有 help_center,没有 knowledge_base。
   改为检查 FeatureHelpCenter("help_center")。

2. feature_flag.go: 中间件从 context 读取 feature_flags,但没有任何
   上游中间件设置它,导致 exists=false 直接 403。GoChat 是自托管非 SaaS,
   无 flags 时应放行(所有功能可用)。

3. feature_flag.go: Account.FeatureFlags 存储为 JSON 对象格式
   ({"help_center":true}),但旧代码只解析 JSON 数组格式。
   新增 JSON 对象解析支持。

测试:新增 JSONObjectFormat / JSONObjectFormatDisabled 用例,
更新 NoFeatureFlags 用例为 200(自托管放行)。
2026-07-30 11:40:02 +08:00
Rogee 92c625ea4a fix: 修复登录后白屏 + label标签闪屏
白屏: security.routes.js 缺少 security_settings_index 命名路由,
SidebarGroup 的 router.resolve 抛错导致所有 sidebar 组件渲染失败.
修复: 添加命名子路由指向空占位组件.

闪屏: 后端 FilterParams.Labels 用 form:"labels" 绑定,
无法解析 Chatwoot 前端的 labels[] 数组参数, label 过滤从未生效.
IntersectionObserver 检测到"还有更多数据"不断触发 loadMore,
8秒内发出 1000+ 次重复请求形成无限循环.
修复: 新增 LabelsArr []string 字段绑定 form:"labels[]",
在 handler 中合并到 Labels 字段.
2026-07-30 10:37:38 +08:00
Rogee 04b999e377 refactor: 移除 FakeMessagePlatform channel 全部逻辑、展示、配置
- 删除 channels/fake/ 整个 Node.js FakeMessagePlatform (8文件)
- 删除后端 FakeProvider (fake.go + fake_test.go) + FakeWebhookHandler (fake_webhook.go)
- bootstrap.go: 移除 FakeProvider 接线 + isFakeChannelEnabled()
- router.go: 移除 FakeWebhook 字段 + /webhooks/fake/:identifier 路由
- provider.go: 移除 ChannelFake 常量
- inbox_service.go: 移除 fake channel 类型验证 + isFakeChannelAllowed()
- 前端: 移除 Fake.vue + ChannelFactory/ChannelList/ChannelItem 引用 + i18n (en/zh_CN)
- package.json: 移除 fake:start/dev/test/dev:all 脚本
- pnpm-workspace.yaml: 移除 channels/fake
- .env/.env.example/quickstart: 移除 GOCHAT_ALLOW_FAKE_CHANNEL
- 测试: bridge_listener_test 改用 web_widget, router_test 移除 fake 路由断言
- 保留 fake:ai LLM Provider (backend/internal/llm/fake_provider.go) — 与 channel 无关
2026-07-30 09:09:17 +08:00
Rogee 92f0d51375 refactor: 统一 DB/Redis 配置为 DSN 模式 + 移除 Helm/K8s 部署
- DatabaseConfig: Host/Port/User/Password/Name/DBName/SSLMode → 单个 DSN 字段
- RedisConfig: Host/Port/Password/DB/URL → 单个 DSN 字段
- 环境变量: GOCHAT_DATABASE_* (7个) → GOCHAT_DATABASE_DSN, GOCHAT_REDIS_* (5个) → GOCHAT_REDIS_DSN
- validator.go: DSN URL 解析校验 (scheme + host)
- redis.go: redis.ParseURL(cfg.DSN) 直连
- 所有 docker-compose / CI / shell 脚本 / .env 同步更新
- 删除 deploy/helm/ 整个目录 (20个文件)
- CI 删除 helm-validate / deploy-staging / deploy-production 三个 job
- 文档同步更新 (README, 架构设计, PRD, 滚动升级)
2026-07-29 20:58:10 +08:00
Rogee 851ca7e372 refactor: 移除 SAML/LDAP/MFA 登录方式,仅保留本地账号密码和 OIDC
后端移除:
- SAML: auth/saml.go, handler/saml_handler.go, account_saml_settings_handler.go,
  model/account_saml_settings.go, model/saml_idp_config.go, repo/*.go
- LDAP: auth/ldap.go, handler/ldap_handler.go, model/account_ldap_settings.go,
  repo/account_ldap_settings_repo.go
- MFA: auth/mfa.go, handler/mfa_handler.go
- auth_service: 移除 mfaService 依赖、MFARequired 字段、LoginWithMFA 方法
- auth_handler: 移除 LoginMFA handler、MFA 分支逻辑
- bootstrap: 移除 SAML/LDAP/MFA service 初始化和 handler 注册
- sso_middleware: 精简为仅支持 OIDC provider
- router: 移除 SAML/LDAP/MFA 路由注册
- config: 移除 SAMLConfig/LDAPConfig struct 和 defaults

前端移除:
- v3/login: 移除 MFA 验证流程和 SAML 登录入口
- v3/api/auth: 移除 MFA 响应处理
- v3/routes: 移除 SSO login 路由
- dashboard: 移除 MFA 设置页面、SAML 安全设置页面
- i18n: 移除 mfa.json
- featureFlags: 移除 SAML feature flag

.env.example / .env: 移除 SAML/LDAP 配置段
2026-07-29 19:03:04 +08:00
Rogee fa6737e258 refactor: 精简配置体系,移除 OAuth 登录/Rate Limit/Admin env 配置
- 移除 .env.example 中 Feature Flags 段(代码中不存在这些 env var)
- 移除 Google/GitHub OAuth 登录认证代码(auth/oauth.go、auth_handler
  OAuthAuthorize/OAuthCallback 路由、auth_service OAuthLogin),保留
  Twitter/Google 作为消息渠道 provider
- 从 OAuthConfig 移除 GitHub 字段(Google 保留供 channel provider 使用)
- 移除 RateLimitConfig 可配置性,RateLimit 中间件改为硬编码 100 req/min、
  60s window,移除 config/validator/reloader 中的 rate_limit 相关代码
- 移除 .env.example 中 GOCHAT_ADMIN_EMAIL/PASSWORD 配置
- 新增 gochat init 命令:交互式或通过 --email/--password/--name flags
  初始化超级管理员账户,创建默认 Account + AccountUser 关联
2026-07-29 16:26:19 +08:00
Rogee dccd4b93f7 fix: 修复第二轮回归发现的已知 BUG
## 修复清单

### 后端路由修复
1.  — 新增 GET 路由与现有 POST 并存
   - 前端 widget.html 原使用 GET 调用但路由仅注册 POST,导致 404
   - 修复: 注册 GET + POST 双路由,handler 支持 query param + JSON body

### 前端修复
2.  — Config 调用方式修正
   - GET /api/v1/widget/config?website_token= → POST /api/v1/widget/config
   - 发送 JSON body: {"website_token":"..."}
   - 后端 Config handler 已同时支持两种调用方式

### 文档更新
3. 修正测试计划 §13.7 已知未实现端点状态
   - 标记 4 项已验证正常(copilot/config、agent_bot_inboxes/、reports/overview)
   - 标记 2 项已修复(widget/config GET+POST)
   - 保留 1 项 P3 未实现(conversation_workflows 需完整 CRUD handler)

### 已验证非 BUG 项
- 仪表盘会话计数的0问题:实际显示 1/13/14 (之前为 stale snapshot)
- 会话列表为空:filter status=open 过滤掉 snoozed 会话 (设计如此)
- Reports API:正确路由 /reports?metric=&since=&until= 
2026-07-28 21:46:53 +08:00
Rogee 9816848ca2 fix: Web Widget SDK + Auto-Reply AgentBot sender + LLM 真实模型对接
## 核心修复

### 1. Auto-Reply Sender 修复(所有渠道)
- AutoReplyListener.sendAutoReply() 通过 botInboxRepo 查询 inbox 关联的 AgentBot
- 使用正确的 SenderType="AgentBot"(非小写 agent_bot)传递真实 AgentBot ID
- bootstrap 注入 agentBotInboxRepo/agentBotRepo 依赖

### 2. 事件数据 BUG 修复(影响所有 Webhook 渠道)
- incoming_persister.dispatch(): 补全 sender_type/content 到 event.Data
- channel/webhook.go: HandleWebhook 同步分发也补全 sender_type/content
- 未补全前 AutoReplyListener 找不到字段直接跳过

### 3. Web Widget SDK 生产验证修复
- cookie → localStorage token 同步(frontend/index.html)
- 路由双注册修复(router.go)
- Vite SPA 模式 + /widget 重写(vite.config.ts)
- WidgetService 注入 Dispatcher 触发事件分发

### 4. LLM 真实模型对接
- 配置 deepseek-v4-flash @ http://10.58.144.6:2014/v1
- LLM-mode auto-reply 规则创建并验证通过
- Prompt 文档落地: docs/captain-ai-auto-replay-prompt.md

### 5. 新增基础设施
- Helm chart (deploy/helm/)
- Widget SDK 生产测试页面
- QA 报告

Closes: BUG-W2 (auth sync), BUG-W3 (route double-reg),
       BUG-WEBHOOK-EVENT (missing event data fields)
2026-07-28 14:03:19 +08:00
Rogee 05c5752a2b fix: Campaign Create 400 + Reports 500 + 种子数据补全
修复:
- campaign_handler.go: Campaign创建JSON解析报VALIDATION_ERROR
- analytics_query_helpers.go: Reports标签/团队维度500(SQL query)
- bootstrap.go: 补充Campaign种子数据确保测试环境完整
2026-07-27 22:01:50 +08:00
Rogee 1c6b8c661c fix: 文件上传Metadata jsonb BUG + search config + fake:ai provider + QA报告
修复:
- message_service.go: 附件Metadata零值字符串''→'{}'(PG jsonb列报错)
- config.dev.yaml/config.development.yaml: search.engine改为db
  (避免依赖未运行的MeiliSearch导致搜索500)

新增:
- fake_provider.go: Captain/AI fake LLM Provider
- test-captain-llm-provider.sh: 测试脚本
- QA测试报告: 93项CDP功能点覆盖验证

文档:
- CDP_TESTING_HANDOFF.md: 进度更新(89%->92%->95%)
- 报告: 11.846~11.897 完整测试日志
2026-07-27 22:01:28 +08:00
rogee c3806b701a feat(parity): align Chatwoot 4.15.1 contracts 2026-07-14 12:11:52 +08:00
rogee d16bb55d91 feat(realtime): complete fake channel reply flow 2026-07-13 14:57:28 +08:00
rogee a712b91982 fix(copilot): harden provider configuration flow 2026-07-13 14:57:28 +08:00
rogee 19cee9af11 fix(copilot): align runtime configuration contracts 2026-07-13 14:57:28 +08:00
rogee 8b9eedc0e2 feat(copilot): finish configuration center 2026-07-13 14:57:28 +08:00
rogee 0a69d80f7c feat(copilot): complete runtime provider configuration 2026-07-13 14:57:28 +08:00
rogee df23caf7d9 feat(copilot): move provider configuration to settings 2026-07-12 22:27:29 +08:00
rogee f39943629f fix: remove enterprise mode gating 2026-07-12 22:19:51 +08:00
rogee 813faa4cd7 fix(messages): normalize webhook reply references 2026-07-12 22:17:04 +08:00
rogee f923791d39 update 2026-07-12 12:20:23 +08:00
rogee 7f2d5579ff fix: stabilize development config and account settings 2026-07-11 17:15:38 +08:00
rogee 690796a7de fix: resolve 4 bugs from QA round 6 (BUG-F/G/H/I)
BUG-F (P1): Fake webhook lookupInbox used JSONB @> operator on a TEXT
column, causing all fake webhooks to return "ignored". Cast
channel_config::jsonb before the @> operator.

BUG-G (P3): Vue Router history.state warning on Activity page. Four
call sites replaced history.state with null/{}, destroying Vue Router's
internal navigation state. Now all replaceState calls preserve
window.history.state.

BUG-H (P3): Inbox list showed stale data because cache_keys endpoint
returned hardcoded "0000000000" for inbox/label/team, so the frontend
IndexedDB cache never invalidated. Cache keys are now derived from
actual DB state (row count + MAX(updated_at)), with defensive fallback
for missing tables.

BUG-I (P3): All worker goroutines shared the same Redis consumer name,
so XINFO CONSUMERS showed 1 consumer instead of N. Each goroutine now
generates a unique consumer ID (workerID-index).
2026-07-10 16:59:26 +08:00
rogee 762de6aa3b feat: migrate worker job dispatch from DB polling to Redis Streams
- worker.go: add Redis fields, XADD on Enqueue, XREADGROUP BLOCK consumer
  loop, sweep compensation for delayed/failed jobs, DB-polling fallback
  when rdb is nil, XGroupCreateMkStream for idempotent consumer groups
- config.go: extend WorkerConfig with stream_prefix, consumer_group,
  block_timeout_s, sweep_interval_s + defaults, env bindings, reloadable
- validator.go: validate new worker Redis fields
- bootstrap.go: fix concurrency bug (NewWorkerPoolWithOptions with Redis
  + cfg.Worker.Concurrency), reorder rdb init before worker pool
- config.dev.yaml/config.prod.yaml: add worker Redis params
- worker_test.go: 6 new miniredis tests (end-to-end, fallback, sweep,
  multi-consumer competition, group creation, Redis failure recovery)
- config_test.go/reloader_test.go: adapt fixtures for new fields
2026-07-10 12:56:32 +08:00
rogee 05af5ebcbc fix: harden fake channel — production guard, token validation, typing events, URL validation, capability narrowing
- Guard fake channel with GOCHAT_ENV check: skip init() registration,
  bootstrap wiring, and inbox creation in production
- Reject empty-token webhooks in production (was silently skipped)
- Use PostgreSQL jsonb @> query for inbox lookup, keep SQLite fallback
- Replace isValidURL string-prefix hack with net/url.Parse
- Handle typing.start/typing.stop by returning nil (no garbage messages)
- Narrow Capabilities to only implemented features (Attachments, Replies)
- Hide fake channel from frontend channel list in production builds
2026-07-10 10:55:54 +08:00
rogee 9c852cd99b add fake channel 2026-07-09 18:14:45 +08:00
rogee 0dabb8cfa5 docs: 整理文档目录结构 — 清理过时文档、归集功能子目录、统一命名规范
清理:
- 删除 34 份过时文档(gap reports/QA临时报告/验收报告/阶段性文档)
- 删除 docs/.hermes/skills 第三方 skills 副本(16 文件)
- 删除 skills-lock.json

目录归集:
- 根目录仅保留 README.md 索引
- product/ — 产品与架构设计(PRD + ARCHITECTURE + P2设计文档 + AI/企业路线图)
- tracking/ — Chatwoot parity 开发跟踪
- requirements/ — M01-M12 模块需求
- plans/ — 历史实现计划
- parity/ — 路由 parity 与前端契约
- qa/ — QA 报告与测试计划
- ops/ — 运维部署

命名规范:
- 全小写 kebab-case,禁止全大写文件名
- product/tracking/ops 用 NN- 序号前缀
- requirements 用 MNN- 两位零填充模块号
- plans/qa 用 YYYY-MM-DD- 日期前缀
- requirements M1-M9 零填充为 M01-M09(修复字典序)

同步更新:
- backend/cmd/route_parity/main.go 路径默认值
- backend/scripts/parity_frontend_smoke.sh 报告路径
- 所有 docs 内部交叉引用
- .gitignore 排除编译产物 (backend/gochat, backend/route_parity)
- 新增迁移 000052/000053
- 前端 WS 相关修改
2026-07-09 14:53:27 +08:00
rogee 8e1caa8f49 Replace hand-written LLM layer with CloudWeGo Eino framework
EinoProvider (eino_provider.go, new):
- Implements GoChat's llm.Provider interface by delegating to Eino's
  model.BaseChatModel (Generate + Stream) and embedding.Embedder
- Converts GoChat ChatMessage ↔ Eino schema.Message (role/content/tool_calls)
- Converts Eino ResponseMeta (FinishReason/Usage) → GoChat ChatResponse
- Converts Eino StreamReader → GoChat onChunk callback for SSE streaming
- Embedder type alias = eino's embedding.Embedder interface

NewProviderFromConfig factory:
- Uses eino-ext/components/model/openai.NewChatModel for chat model
- Uses eino-ext/components/embedding/openai.NewEmbedder for embeddings
- Works for all OpenAI-compatible providers (OpenAI/Azure/Ark/Doubao/Qwen)
  by setting llm_base_url in config
- Graceful fallback to hand-written OpenAIProvider if Eino init fails
- Removed previous Anthropic provider switch (Eino's OpenAI impl handles
  Claude via OpenAI-compat endpoint)

Dependencies added:
- github.com/cloudwego/eino v0.9.12 (core framework)
- github.com/cloudwego/eino-ext/components/model/openai v0.1.13
- github.com/cloudwego/eino-ext/components/embedding/openai

Verified:
- go build ./... passes
- go vet passes (llm + app packages)
- go test passes (llm + service, SQLite mode)
- Server starts with Eino provider initialized
- All existing routes work (assistants, auto-reply, RAG, conversation)
- RAG query reaches Eino provider (fails on LLM call without API key,
  confirming Eino is the active provider)
2026-07-08 15:38:08 +08:00
rogee 6d8eda28b5 Phase 3.4: Context window optimization — token estimation + sliding window
- token_estimator.go (new): TokenEstimator with ~4 chars/token heuristic,
  EstimateText/EstimateMessages methods, TruncateMessages sliding window
  that drops oldest messages to fit token budget, BuildContextWindow
  entry point that converts conversation messages to LLM format with
  token-budgeted truncation (default 4096 tokens)
- copilot_context_service.go: fetch up to 100 messages (was 20), then
  apply BuildContextWindow truncation to fit within 4096 token budget;
  log how many messages were dropped

Verified: go build + go vet + go test all pass
Semantic search route reaches handler (times out on LLM call without API key,
confirming route + service wiring is correct)
2026-07-08 15:38:08 +08:00
rogee af5c7c6bc7 Phase 3.3: Multi LLM Provider support (Anthropic Claude)
- anthropic_provider.go (new): AnthropicProvider implementing the Provider
  interface using Claude's messages API. Handles:
  - System prompt as top-level param (not in messages array)
  - Content blocks response format → extract text
  - SSE streaming with Anthropic event types (content_block_delta, message_stop)
  - Anthropic-specific headers (x-api-key, anthropic-version)
  - Retry with exponential backoff (shared logic with OpenAI provider)
  - Embedding API returns error (Anthropic has no embeddings; OpenAI-compat
    provider should be used for embeddings)
- NewProviderFromConfig factory function: selects AnthropicProvider for
  provider="anthropic"/"claude", OpenAIProvider for all others
- bootstrap.go: use NewProviderFromConfig instead of hardcoded NewOpenAIProvider,
  allowing config.captain.llm_provider to switch between providers

Note: domestic providers (Volcengine/Doubao/Qwen) use OpenAI-compatible API
and work with the existing OpenAIProvider by setting llm_base_url.

Verified: go build + go vet + go test all pass
2026-07-08 15:38:08 +08:00
rogee b2f36a20a0 Phase 3.2: Function Calling — tool_call loop for AI services
- llm/provider.go: extend ChatMessage with ToolCalls, ToolCallID, Name
  fields; add ToolCall + ToolCallFunction structs for parsing LLM
  function call responses
- tool_execution_service.go (new): ToolExecutionService that converts
  CaptainCustomTool → LLM ToolDefinition, executes HTTP tool calls
  (GET/POST/PUT with bearer/basic/api-key auth), and runs the full
  tool_call loop (LLM → tool_call → execute → result → LLM → final
  answer) with maxIterations safeguard
- captain_conversation_service.go: add toolExecSvc field +
  SetToolExecutionService method; use RunToolCallLoop in
  generateConversationResponse when tools are available, with graceful
  fallback to plain LLM call on error
- bootstrap.go: instantiate ToolExecutionService and inject into
  CaptainConversationService

Verified: go build + go vet + go test all pass
2026-07-08 15:38:08 +08:00
rogee c72e359e48 Phase 3.1: Help Center semantic search with pgvector
- ArticleEmbeddingRepo (new): Upsert, GetByArticleID, DeleteByArticleID,
  SearchByEmbedding using pgvector cosine distance (vector_embedding column)
- ArticleEmbedding model: add VectorEmbedding pgvector.Vector field
  alongside existing JSONB Embedding (backward compatible)
- ArticleService: add SemanticSearch() — generates query embedding via LLM,
  searches articles by cosine similarity; add GenerateEmbedding() — creates
  and stores article embedding from title+description+content
- ArticleHandler: add SemanticSearch endpoint
  GET /portals/:portal_id/articles/semantic_search?query=...
- bootstrap.go: inject articleEmbeddingRepo + llmProvider into ArticleService
- router.go: register /articles/semantic_search route
- migration 000049: add vector(1536) column to article_embeddings table,
  create ivfflat index, migrate existing JSONB data to vector format

Verified: go build + go vet + go test all pass
2026-07-08 15:38:08 +08:00
rogee b769b9a3e4 Phase 2: AutoReplyRule integration + AgentBot Captain type
AutoReplyRule complete integration:
- bootstrap.go: instantiate AutoReplyRuleService + AutoReplyListener,
  register listener on channel dispatcher for message.created events
- router.go: register /captain/auto_reply_rules CRUD + /evaluate routes
- auto_reply_rule_handler.go: fix c.Param(id) → c.Param(account_id),
  override evalCtx.AccountID from path param
- auto_reply_rule_service.go: add JSON tags to AutoReplyEvaluationContext
  for correct request body binding
- auto_reply_listener.go (new): EventListener that triggers on incoming
  messages, evaluates active rules, composes reply (static/LLM/mixed),
  respects DelaySeconds and OneTimeOnly flags, sends via MessageService
- migration 000048: create captain_auto_reply_rules table

AgentBot + Captain integration:
- agent_bot_listener.go: add captainConvSvc field + SetCaptainConversationService
  method. In HandleEvent loop, check bot.BotType == captain and route
  to CaptainConversationService.BuildConversationResponseByAccount
  instead of webhook push. Extract assistant_id from bot.Config JSONB,
  extract conversation_id from event data.
- bootstrap.go: inject captainConversationService into agentBotListener

CaptainConversationService improvement:
- generateConversationResponse: use assistant config for system prompt,
  model name, and temperature instead of hardcoded values

Verified:
- go build ./... passes
- go vet passes on all internal packages
- go test passes (service + repository + llm, SQLite mode)
- Auto-reply CRUD: create/get/update/delete all work
- Auto-reply evaluate: correctly matches hello → should_reply=true,
  correctly rejects non-matching message
- Existing routes unaffected (assistants, RAG, conversation respond)
- Migration 000048 creates captain_auto_reply_rules table successfully
2026-07-08 15:38:08 +08:00
rogee 42b8b6c7f9 Activate Captain AI features: config, RAG routes, conversation handler
Phase 1 of AI_FEATURE_ROADMAP.md:

1. config.yaml: Add captain configuration section (llm_provider, llm_model,
   llm_api_key, llm_base_url, embedding_model, embedding_dims, max_tokens,
   temperature). Secrets injected via GOCHAT_CAPTAIN_LLM_API_KEY env var.

2. RAG route registration:
   - bootstrap.go: instantiate RAGService + RAGHandler, add to Handlers
   - router.go: register POST /captain/rag/query + /captain/rag/index/:response_id
   - rag_handler.go: fix account_id param name (was "id", route uses "account_id")

3. CaptainConversationService activation:
   - Remove "_ = captainConversationService" ignore in bootstrap.go
   - Create CaptainConversationHandler with BuildResponse endpoint
   - Register POST /captain/conversations/:conversation_id/respond route
   - Fix account_id param name in handler

Verified:
- go build ./... passes
- go vet passes on all modified packages
- go test passes (llm + service packages)
- Server starts with captain config loaded
- RAG query/index routes return proper handler responses (not 404)
- Conversation respond route returns proper skip for non-pending conversations
- Existing captain routes unaffected (assistants list still works)
2026-07-08 15:38:08 +08:00
rogee 10cc300f31 Fix canned_responses 500: add missing SQL migration
The canned_responses table was registered in autoMigrate() (app.go) but
autoMigrate() is never called on the serve path (Bootstrap -> NewDatabase
-> RunMigrations only). With no SQL migration file, the table never
existed, so POST /api/v1/accounts/:id/canned_responses failed with
'relation does not exist' -> 500.

Add migration 000049 to create the table matching the GORM model
(id, account_id, content, short_code, timestamps) with the account+
short_code partial unique index and deleted_at index.

Also include two related fixes staged in the working tree:
- router.go: register notifications.GET("") alongside GET("/") to avoid
  Gin 301 trailing-slash redirect that the Vite proxy doesn't follow
- notifications/actions.js: stop the infinite loader on fetch error to
  prevent IntersectionObserver re-fire loop
2026-07-08 11:43:34 +08:00
rogee fe0881c02b Update TestAccountDefaultValues assertion to match zh_CN default locale
Account.Locale gorm default was changed from 'en' to 'zh_CN' in the
locale cleanup commit (a1ae852), but the default-values test still
asserted 'en', causing TestAccountDefaultValues to fail. Align the
assertion with the new default.
2026-07-08 10:13:58 +08:00