清理: - 删除 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 相关修改
520 lines
27 KiB
Markdown
520 lines
27 KiB
Markdown
# M9: 知识库与帮助中心(Portal / Category / Article / Help Center / Kbase Search)
|
||
|
||
> 参照仓库:chatwoot-reference
|
||
> 产出日期:2026-05-22
|
||
> 版本基准:Chatwoot v3.x
|
||
|
||
---
|
||
|
||
## 目录
|
||
|
||
1. [Portal CRUD + 设置 + Logo + 自定义域名](#1-portal-crud--设置--logo--自定义域名)
|
||
2. [Category / Folder 管理](#2-category--folder-管理)
|
||
3. [Article CRUD + 关联文章 + 排序 + 搜索](#3-article-crud--关联文章--排序--搜索)
|
||
4. [RelatedCategory 关联分类](#4-relatedcategory-关联分类)
|
||
5. [Help Center 前端展示(Portal布局/SEO/追踪像素)](#5-help-center-前端展示portal布局seo追踪像素)
|
||
6. [Kbase 文本搜索(PgSearch)](#6-kbase-文本搜索pgsearch)
|
||
7. [Article Embedding 向量搜索(企业版)](#7-article-embedding-向量搜索企业版)
|
||
8. [Article 批量操作(状态/删除/翻译)](#8-article-批量操作状态删除翻译)
|
||
9. [权限策略(PortalPolicy / ArticlePolicy / CategoryPolicy)](#9-权限策略portalpolicy--articlepolicy--categorypolicy)
|
||
10. [AI辅助:Help Center 策展 + Article Writer(企业版)](#10-ai辅助help-center-策展--article-writer企业版)
|
||
|
||
---
|
||
|
||
## 1. Portal CRUD + 设置 + Logo + 自定义域名
|
||
|
||
### 功能描述
|
||
Portal 是帮助中心的顶层容器,每个 Portal 属于一个 Account,可绑定一个 WebWidget 渠道用于在线聊天。Portal 拥有完整的配置体系:名称、slug(唯一标识)、自定义域名、品牌颜色、header 文字、首页链接、布局模式(classic/documentation)、多语言设置、社交链接、SSL 配置等。
|
||
|
||
### 用户操作流程
|
||
1. **创建 Portal**:管理员进入 Settings → Help Center → 点击 "Create Portal",填写名称、slug、颜色、布局、默认语言等;系统自动创建 Portal,可选绑定 WebWidget 渠道
|
||
2. **查看 Portal 列表**:按账户加载所有 Portal(含 logo、archived 状态等)
|
||
3. **更新 Portal 设置**:修改名称、header_text、homepage_link、custom_domain、颜色、布局、多语言配置(allowed_locales/default_locale/draft_locales)、社交链接等
|
||
4. **删除 Portal**:级联删除 Categories、Articles、Folders、Inbox 关联(nullify)
|
||
5. **归档 Portal**:将 Portal 标记为 archived(archived=true),归档后前端不展示
|
||
6. **上传/移除 Logo**:Portal 支持品牌 Logo(ActiveStorage attachment),创建/更新时传入 blob_id
|
||
7. **发送 DNS 配置邮件**:为自定义域名发送 DNS 设置指引邮件(send_instructions),接收方按邮件指引配置 CNAME
|
||
8. **查看 SSL 状态**:检查自定义域名的 SSL 证书状态
|
||
|
||
### 涉及的API端点
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/api/v1/accounts/{account_id}/portals` | 列出所有 Portal |
|
||
| POST | `/api/v1/accounts/{account_id}/portals` | 创建 Portal |
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{id}` | 查看 Portal 详情(含 articles) |
|
||
| PATCH | `/api/v1/accounts/{account_id}/portals/{id}` | 更新 Portal 设置 |
|
||
| DELETE | `/api/v1/accounts/{account_id}/portals/{id}` | 删除 Portal |
|
||
| PATCH | `/api/v1/accounts/{account_id}/portals/{id}/archive` | 归档 Portal |
|
||
| DELETE | `/api/v1/accounts/{account_id}/portals/{id}/logo` | 移除 Logo |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{id}/send_instructions` | 发送 DNS 配置邮件 |
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{id}/ssl_status` | 查看 SSL 状态 |
|
||
|
||
### 涉及的数据模型 + 关键字段
|
||
|
||
- **Portal** 模型(表 `portals`):
|
||
- `id` (bigint, PK)
|
||
- `name` (string, not null) — 帮助中心名称
|
||
- `slug` (string, not null, unique) — 唯一标识,用于 URL 路径
|
||
- `archived` (boolean, default false) — 归档标记
|
||
- `color` (string) — 品牌主题色,默认 `#1f93ff`
|
||
- `header_text` (text) — 头部文案
|
||
- `homepage_link` (string) — 首页链接
|
||
- `page_title` (string) — SEO 页面标题
|
||
- `custom_domain` (string, unique) — 自定义域名
|
||
- `config` (jsonb) — 配置 JSON:`{ allowed_locales, default_locale, draft_locales, layout, social_profiles }`
|
||
- `ssl_settings` (jsonb, not null) — SSL 证书配置
|
||
- `account_id` (integer, not null, FK) — 所属账户
|
||
- `channel_web_widget_id` (bigint, FK) — 关联的 WebWidget 渠道(可选,用于嵌入在线聊天)
|
||
- `created_at`, `updated_at`
|
||
- 关联:
|
||
- `has_many :categories, dependent: :destroy_async`
|
||
- `has_many :folders, through: :categories`
|
||
- `has_many :articles, dependent: :destroy_async`
|
||
- `has_one_attached :logo` — ActiveStorage Logo
|
||
- `has_many :inboxes, dependent: :nullify` — Inbox 可关联 Portal
|
||
- `belongs_to :channel_web_widget` — 可选绑定在线聊天
|
||
|
||
- 关键方法:
|
||
- `default_locale` — 从 config 中读取默认语言
|
||
- `allowed_locale_codes` — 返回 Portal 支持的语言列表
|
||
- `public_locale_codes` — 已发布文章可见语言(排除 draft_locale)
|
||
- `layout` — 返回布局模式(classic / documentation)
|
||
- `social_profiles` — 社交链接配置
|
||
- `config_json_format` — before_validation 回调,将 config 规范化(确保 locale 列表有效)
|
||
|
||
### 涉及的业务逻辑
|
||
- `PortalsController#create` — 构建 Portal + 合并 live_chat_widget_params(可选创建 WebWidget 渠道)
|
||
- `PortalsController#update` — 事务更新 Portal + 处理 Logo attachment
|
||
- `PortalsController#archive` — 设置 `archived=true`
|
||
- `PortalsController#send_instructions` — 验证邮箱格式后发送 DNS 配置邮件
|
||
- `PortalsController#show` — 加载 Portal 及其 articles(可按 locale 搜索)
|
||
- `Portal#validate_slug_uniqueness` — slug 全局唯一校验
|
||
- `Portal#normalize_empty_string_to_nil` — 将 custom_domain/homepage_link 空字符串转为 nil
|
||
|
||
---
|
||
|
||
## 2. Category / Folder 管理
|
||
|
||
### 功能描述
|
||
Category(分类)是 Portal 下的内容组织单元,支持层级结构(parent_category / sub_categories)和多语言。每个 Category 属于一个 Portal 和一种 locale。Folder(文件夹)是 Category 内的进一步分组,Article 可以归属于 Folder。
|
||
|
||
### 用户操作流程
|
||
1. **创建 Category**:在 Portal 管理页面选择 "Add Category",填写名称、描述、图标、locale、父分类(可选);同时可指定关联分类(related_category_ids)
|
||
2. **查看 Category 列表**:按 Portal + locale 加载分类列表,按 position 排序
|
||
3. **更新 Category**:修改名称、描述、图标、关联分类等
|
||
4. **删除 Category**:级联删除 Folders,Articles 的 category_id 被设为 NULL
|
||
5. **排序 Category**:通过 reorder 接口批量更新 position
|
||
|
||
### 涉及的API端点
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories` | 列出分类(可按 locale 过滤) |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories` | 创建分类 |
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories/{id}` | 查看分类详情 |
|
||
| PATCH | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories/{id}` | 更新分类 |
|
||
| DELETE | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories/{id}` | 删除分类 |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{portal_id}/categories/reorder` | 批量排序 |
|
||
|
||
### 涉及的数据模型 + 关键字段
|
||
|
||
- **Category** 模型(表 `categories`):
|
||
- `id` (bigint, PK)
|
||
- `name` (string) — 分类名称
|
||
- `description` (text) — 分类描述
|
||
- `icon` (string, default "") — 图标标识
|
||
- `locale` (string, default "en") — 语言
|
||
- `slug` (string, not null) — URL slug
|
||
- `position` (integer) — 排序位置
|
||
- `account_id` (integer, not null, FK)
|
||
- `portal_id` (integer, not null, FK)
|
||
- `parent_category_id` (bigint, FK) — 父分类 ID(层级结构)
|
||
- `associated_category_id` (bigint, FK) — 关联分类 ID(跨语言关联)
|
||
- 索引:`(slug, locale, portal_id)` 联合唯一索引
|
||
- 关联:
|
||
- `has_many :folders, dependent: :destroy_async`
|
||
- `has_many :articles, dependent: :nullify`
|
||
- `has_many :category_related_categories (RelatedCategory), dependent: :destroy_async`
|
||
- `has_many :related_categories (Category), through: :category_related_categories`
|
||
- `has_many :sub_categories (Category), foreign_key: :parent_category_id`
|
||
- `belongs_to :parent_category (Category), optional: true`
|
||
- `belongs_to :associated_category (Category), optional: true` — 同内容的另一种语言版本
|
||
- `belongs_to :portal`
|
||
|
||
- **Folder** 模型(表 `folders`):
|
||
- `id` (bigint, PK)
|
||
- `name` (string) — 文件夹名称
|
||
- `account_id` (integer, not null, FK)
|
||
- `category_id` (integer, not null, FK)
|
||
- 关联:`has_many :articles, dependent: :nullify`
|
||
|
||
- 关键方法:
|
||
- `Category.search(params)` — 按 locale 过滤 + 分页 + position 排序
|
||
- `Category.update_positions(portal, positions_hash)` — 批量更新 position
|
||
- Category 创建时自动从 Portal 继承 `account_id`
|
||
- Category 校验 `locale` 必须在 Portal 的 `allowed_locales` 范围内
|
||
- `(slug, locale, portal_id)` 联合唯一约束
|
||
|
||
---
|
||
|
||
## 3. Article CRUD + 关联文章 + 排序 + 搜索
|
||
|
||
### 功能描述
|
||
Article(文章)是帮助中心的核心内容单元,属于 Portal + Category(可选 Folder),支持草稿/发布/归档三种状态。文章可关联"根文章"(associated_article_id)实现多语言翻译链接,支持全文搜索(PgSearch)、排序、浏览量统计。
|
||
|
||
### 用户操作流程
|
||
1. **创建 Article**:在 Portal 管理页面选择 "Add Article",填写标题、内容、描述、locale、作者、分类;默认状态为 draft;可选择关联已有文章(associated_article_id)
|
||
2. **查看 Article 列表**:按 Portal + 可选 category_slug/locale/author/status 过滤,支持全文搜索(query),有分类时按 position 排序,否则按 updated_at 排序
|
||
3. **查看 Article 详情**:获取单篇文章完整内容
|
||
4. **编辑 Article**:获取编辑数据(edit 接口)
|
||
5. **更新 Article**:修改标题、内容、状态、分类等
|
||
6. **删除 Article**:删除单篇文章
|
||
7. **排序 Articles**:通过 reorder 接口批量更新 position
|
||
8. **查看浏览量**:Article.views 字段,每次公开页面访问时通过 tracking pixel 增量
|
||
|
||
### 涉及的API端点
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles` | 列出文章(支持搜索+过滤) |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles` | 创建文章 |
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/{id}` | 查看文章详情 |
|
||
| GET | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/{id}/edit` | 编辑数据 |
|
||
| PATCH | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/{id}` | 更新文章 |
|
||
| DELETE | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/{id}` | 删除文章 |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/reorder` | 批量排序 |
|
||
|
||
### 涉及的数据模型 + 关键字段
|
||
|
||
- **Article** 模型(表 `articles`):
|
||
- `id` (bigint, PK)
|
||
- `title` (string) — 标题
|
||
- `content` (text) — 正文内容
|
||
- `description` (text) — 简短描述(用于列表展示+搜索权重B)
|
||
- `locale` (string, default "en", not null) — 语言
|
||
- `slug` (string, not null, unique) — URL slug
|
||
- `status` (integer) — 状态枚举:draft(0), published(1), archived(2)
|
||
- `position` (integer) — 排序位置
|
||
- `views` (integer) — 浏览次数
|
||
- `meta` (jsonb) — 元数据 JSON
|
||
- `account_id` (integer, not null, FK)
|
||
- `portal_id` (integer, not null, FK)
|
||
- `category_id` (integer, FK) — 所属分类(可选)
|
||
- `folder_id` (integer, FK) — 所属文件夹(可选)
|
||
- `author_id` (bigint, FK) — 作者(User)
|
||
- `associated_article_id` (bigint, FK) — 根文章 ID(多语言关联)
|
||
- 关联:
|
||
- `has_many :associated_articles (Article), foreign_key: :associated_article_id, dependent: :nullify`
|
||
- `belongs_to :root_article (Article), foreign_key: :associated_article_id`
|
||
- `belongs_to :category, optional: true`
|
||
- `belongs_to :folder, optional: true`
|
||
- `belongs_to :portal`
|
||
- `belongs_to :author (User)`
|
||
- 搜索 scope:
|
||
- `search_by_category_slug`, `search_by_category_locale`, `search_by_locale`, `search_by_author`, `search_by_status`
|
||
- `order_by_updated_at`, `order_by_position`, `order_by_views`
|
||
- `pg_search_scope :text_search` — 全文搜索(title:A, description:B, content:C),tsearch prefix 模式
|
||
|
||
- 关键方法:
|
||
- `Article.search(params)` — 组合过滤 + 全文搜索
|
||
- `Article.associate_root_article(associated_article_id)` — 建立多语言关联链
|
||
- `Article.find_root_article_id(article)` — 查找根文章 ID
|
||
- `Article.update_positions(portal, positions_hash)` — 批量排序
|
||
- `Article#increment_view_count` — 浏览量 +1(跳过 validation)
|
||
- `before_validation` 钩子:确保 account_id、slug、locale 自动填充
|
||
|
||
### 涉及的业务逻辑
|
||
- `ArticlesController#index` — 先计算统计数(all_count、mine_count、published_count、draft_count),再执行搜索 + 分页
|
||
- `ArticlesController#create` — 默认 status=draft,创建后调用 `associate_root_article`
|
||
- `ArticlesController#set_article_count` — 不含 status/author_id 的搜索计算各种状态文章数
|
||
|
||
---
|
||
|
||
## 4. RelatedCategory 关联分类
|
||
|
||
### 功能描述
|
||
RelatedCategory 是分类之间的关联关系表,用于在帮助中心展示"相关分类"推荐。这是一种多对多关系:一个 Category 可以关联多个其他 Category(通过 RelatedCategory 中间表)。
|
||
|
||
### 用户操作流程
|
||
1. **创建/更新 Category 时指定关联分类**:在 category 参数中传入 `related_category_ids` 数组
|
||
2. **前端展示相关分类**:在分类详情页面展示关联的其他分类
|
||
|
||
### 涉及的API端点
|
||
同 Category CRUD(通过 `related_category_ids` 参数)
|
||
|
||
### 涉及的数据模型 + 关键字段
|
||
|
||
- **RelatedCategory** 模型(表 `related_categories`):
|
||
- `id` (bigint, PK)
|
||
- `category_id` (bigint, FK) — 源分类
|
||
- `related_category_id` (bigint, FK) — 关联分类
|
||
- `created_at`, `updated_at`
|
||
- 索引:`(category_id, related_category_id)` 和 `(related_category_id, category_id)` 双向唯一索引
|
||
- 关联:
|
||
- `belongs_to :related_category (Category)`
|
||
- `belongs_to :category (Category)`
|
||
- Category 中:
|
||
- `has_many :category_related_categories (RelatedCategory), dependent: :destroy_async`
|
||
- `has_many :related_categories (Category), through: :category_related_categories`
|
||
|
||
---
|
||
|
||
## 5. Help Center 前端展示(Portal布局/SEO/追踪像素)
|
||
|
||
### 功能描述
|
||
帮助中心有公开的 Portal 前端,通过 `/hc/{slug}/{locale}` 系列路由对外展示。支持两种布局模式(classic / documentation),支持 SEO(OG 图片、sitemap)、自定义域名、暗/亮主题切换、iframe 嵌入、追踪像素(tracking pixel)统计浏览量。
|
||
|
||
### 用户操作流程
|
||
1. **访问帮助中心首页**:`/hc/{slug}` 自动重定向到 `/hc/{slug}/{default_locale}`,加载 Portal 配置、分类、热门文章、精选文章
|
||
2. **浏览分类页**:`/hc/{slug}/{locale}/categories/{category_slug}`,展示分类下的已发布文章
|
||
3. **阅读文章**:`/hc/{slug}/articles/{article_slug}`,渲染文章内容、OG SEO 信息
|
||
4. **搜索文章**:`/hc/{slug}/{locale}/articles?query=xxx`,前端搜索+排序+分页
|
||
5. **Markdown 输出**:`/hc/{slug}/articles/{article_slug}.md`,纯 Markdown 格式(供 AI/工具调用)
|
||
6. **Tracking Pixel**:`/hc/{slug}/articles/{article_slug}.png`,1x1 像素追踪浏览量(24h private cache)
|
||
7. **Sitemap**:`/hc/{slug}/sitemap.xml`,SEO 爬虫用
|
||
8. **自定义域名**:Portal 配置 custom_domain 后,DNS CNAME 指向 Chatwoot,SSL 自动配置
|
||
|
||
### 涉及的API端点(Public)
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| GET | `/hc/{slug}` | Portal 首页(重定向到 locale 版) |
|
||
| GET | `/hc/{slug}/sitemap.xml` | Sitemap |
|
||
| GET | `/hc/{slug}/{locale}` | Portal 首页(指定语言) |
|
||
| GET | `/hc/{slug}/{locale}/articles` | 文章搜索列表 |
|
||
| GET | `/hc/{slug}/{locale}/categories` | 分类列表(JSON) |
|
||
| GET | `/hc/{slug}/{locale}/categories/{category_slug}` | 分类详情+文章 |
|
||
| GET | `/hc/{slug}/{locale}/categories/{category_slug}/articles` | 分类下文章 |
|
||
| GET | `/hc/{slug}/articles/{article_slug}` | 文章详情页 |
|
||
| GET | `/hc/{slug}/articles/{article_slug}.md` | Markdown 输出 |
|
||
| GET | `/hc/{slug}/articles/{article_slug}.png` | Tracking Pixel |
|
||
|
||
### 涉及的业务逻辑
|
||
|
||
- **Public::Api::V1::PortalsController**:
|
||
- `show` — 加载 Portal + 文章 + 分类,按 locale 过滤
|
||
- `load_home_data`(documentation 布局)— 加载 visible_categories、popular_topics、featured articles、recently_articles
|
||
- 自定义域名验证:`ensure_custom_domain_request`
|
||
- 布局选择:`set_portal_layout`(classic/documentation)
|
||
- 视图变体:`set_view_variant`(documentation 布局使用 :documentation variant)
|
||
|
||
- **Public::Api::V1::Portals::CategoriesController**:
|
||
- `index` — HTML 重定向到 locale 版,JSON 返回分类列表
|
||
- `show` — 加载分类详情 + 已发布文章(documentation 布局)
|
||
|
||
- **Public::Api::V1::Portals::ArticlesController**:
|
||
- `index` — 搜索 + 排序(sort: newest/popular) + 限制结果数
|
||
- `show` — 渲染文章内容(OG 图片)
|
||
- `show_markdown` — 纯 Markdown 输出(仅已发布文章)
|
||
- `tracking_pixel` — 增加浏览量 + 返回 1x1 PNG(24h private cache)
|
||
|
||
- **Public::Api::V1::Portals::BaseController**:
|
||
- `PORTAL_LAYOUTS = %w[classic documentation]`
|
||
- Locale 切换:`switch_locale_with_portal` / `switch_locale_with_article`
|
||
- 主题:dark/light 通过 params[:theme]
|
||
- iframe 允许:`allow_iframe_requests`
|
||
- 功能开关:`ensure_portal_feature_enabled`
|
||
|
||
---
|
||
|
||
## 6. Kbase 文本搜索(PgSearch)
|
||
|
||
### 功能描述
|
||
Article 模型使用 PgSearch(PostgreSQL 全文搜索)实现知识库文本搜索。搜索权重:标题(A)> 描述(B)> 正文(C),使用 tsearch prefix 模式(支持前缀匹配),normalization=2 防止长文堆叠关键词。
|
||
|
||
### 用户操作流程
|
||
1. **管理端搜索**:在 Portal 管理页面的文章列表中输入搜索词(query 参数),结合 category_slug/locale/author/status 过滤
|
||
2. **公众端搜索**:在帮助中心前端搜索框输入关键词,搜索已发布文章
|
||
|
||
### 搜索实现细节
|
||
|
||
- **pg_search_scope :text_search**:
|
||
- `against: { title: 'A', description: 'B', content: 'C' }` — 权重分级
|
||
- `using: { tsearch: { prefix: true, normalization: 2 } }` — 前缀匹配 + log(document_length) 归一化
|
||
- `ranked_by: ':tsearch'`
|
||
|
||
- **Article.search(params)**:
|
||
- 先 left_outer_joins(:category) + 各种 scope 过滤
|
||
- 再 `text_search(params[:query])` 如果 query 存在
|
||
- 返回结果集
|
||
|
||
- **公众端搜索**:
|
||
- `Public::Api::V1::Portals::ArticlesController#index`
|
||
- 仅搜索已发布文章(`published` 状态)
|
||
- 支持 sort 参数(newest/popular)
|
||
- 企业版覆盖 `search_articles` → 使用向量搜索(见下节)
|
||
|
||
---
|
||
|
||
## 7. Article Embedding 向量搜索(企业版)
|
||
|
||
### 功能描述
|
||
企业版提供基于向量嵌入(Embedding)的语义搜索功能。当文章标题/描述/内容变更时,自动生成 embedding 向量存入 `article_embeddings` 表(使用 pgvector 扩展,1536维 cosine 距离)。公众端搜索时可使用向量搜索替代文本搜索,返回语义最相关的5篇文章。
|
||
|
||
### 用户操作流程
|
||
1. **自动嵌入生成**:文章保存后自动触发 `Portal::ArticleIndexingJob` → 生成搜索词 → `Captain::Llm::EmbeddingService` 调用 LLM API 生成向量 → 存入 `article_embeddings`
|
||
2. **向量搜索**:公众端搜索时,若 Portal 账户开启了 `help_center_embedding_search` 功能开关,使用 `vector_search` 替代文本搜索
|
||
|
||
### 涉及的数据模型 + 关键字段
|
||
|
||
- **ArticleEmbedding** 模型(表 `article_embeddings`):
|
||
- `id` (bigint, PK)
|
||
- `embedding` (vector(1536)) — 向量数据(pgvector 扩展)
|
||
- `term` (text, not null) — 搜索词文本
|
||
- `article_id` (bigint, not null, FK)
|
||
- `created_at`, `updated_at`
|
||
- 索引:`ivfflat` 索引(向量近似搜索)
|
||
- 关联:
|
||
- `belongs_to :article`
|
||
- `has_neighbors :embedding, normalize: true` — Neighbor 模式(cosine 距离)
|
||
|
||
### 涉及的业务逻辑
|
||
|
||
- **Enterprise::Concerns::Article**(prepend 到 Article 模型):
|
||
- `after_save :add_article_embedding` — 当 title/description/content 变更时触发
|
||
- `self.vector_search(params)`:
|
||
1. 调用 `Captain::Llm::EmbeddingService` 将 query 生成向量
|
||
2. 在已过滤文章范围内,使用 `ArticleEmbedding.nearest_neighbors(:embedding, embedding, distance: 'cosine').limit(5)` 找最近的5篇
|
||
3. 返回匹配文章
|
||
|
||
- **Enterprise::Public::Api::V1::Portals::ArticlesController**(prepend):
|
||
- `search_articles` 覆盖:若 `help_center_embedding_search` 开关开启且有 query,使用 `vector_search`;否则走原始文本搜索
|
||
|
||
- **Portal::ArticleIndexingJob**:
|
||
- `perform(article)` → 调用 `article.generate_and_save_article_seach_terms` 生成搜索词
|
||
|
||
- **ArticleEmbedding#update_response_embedding**(after_commit):
|
||
- 当 term 变更或 embedding 为 nil 时,调用 `Captain::Llm::UpdateEmbeddingJob` 重新生成向量
|
||
|
||
---
|
||
|
||
## 8. Article 批量操作(状态/删除/翻译)
|
||
|
||
### 功能描述
|
||
提供文章的批量操作能力:批量修改状态(draft/published/archived)、批量删除、批量翻译(企业版,调用 Captain LLM 翻译服务)。
|
||
|
||
### 用户操作流程
|
||
1. **批量修改状态**:在文章列表选中多篇文章,选择目标状态(draft/published/archived),调用 `update_status`
|
||
2. **批量删除**:选中多篇文章,调用 `delete_articles`
|
||
3. **批量翻译**(企业版):选中多篇文章 + 目标 locale + 目标分类,调用 `translate`;系统检查是否已存在翻译(若有则返回冲突提示,可 force 覆盖),然后异步执行 `Captain::Articles::TranslateJob`
|
||
|
||
### 涉及的API端点
|
||
|
||
| 方法 | 路径 | 说明 |
|
||
|------|------|------|
|
||
| PATCH | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/bulk_actions/update_status` | 批量修改状态 |
|
||
| DELETE | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/bulk_actions/delete_articles` | 批量删除 |
|
||
| POST | `/api/v1/accounts/{account_id}/portals/{portal_id}/articles/bulk_actions/translate` | 批量翻译(企业版) |
|
||
|
||
### 涉及的业务逻辑
|
||
|
||
- **BulkActionsController**(核心版):
|
||
- `update_status` — 验证状态有效性,事务更新所有选中文章
|
||
- `delete_articles` — 批量 destroy_all
|
||
- `translate` — 基础版返回 `not_implemented`
|
||
|
||
- **Enterprise::BulkActionsController**(prepend):
|
||
- `translate` 覆盖实现:
|
||
1. 验证 captain_tasks 功能开关
|
||
2. 验证 locale 在 Portal allowed_locales 中
|
||
3. 验证目标分类存在且 locale 匹配
|
||
4. 检查翻译是否已存在(`find_existing_translations`),若有返回 409 conflict(含重复文章列表),可 force=true 覆盖
|
||
5. 为每篇文章异步执行 `Captain::Articles::TranslateJob`
|
||
|
||
- **Captain::Articles::TranslateJob**:
|
||
- 调用 `Captain::Llm::ArticleTranslationService` 分别翻译标题和内容
|
||
- 若已有翻译则更新,否则创建新文章(关联到根文章)
|
||
|
||
---
|
||
|
||
## 9. 权限策略(PortalPolicy / ArticlePolicy / CategoryPolicy)
|
||
|
||
### 功能描述
|
||
知识库相关资源的权限策略:基础版仅管理员可操作(CRUD),普通账户成员可查看(index/show)。企业版扩展了自定义角色权限 `knowledge_base_manage`。
|
||
|
||
### 涉及的权限策略
|
||
|
||
- **PortalPolicy**:
|
||
- `index?` / `show?` / `ssl_status?` → 账户成员即可
|
||
- `create?` / `update?` / `destroy?` / `edit?` / `logo?` / `send_instructions?` → 仅管理员
|
||
|
||
- **CategoryPolicy**:
|
||
- `index?` → 账户成员
|
||
- `create?` / `update?` / `destroy?` / `show?` / `edit?` / `reorder?` → 仅管理员
|
||
|
||
- **ArticlePolicy**:
|
||
- `index?` → 账户成员
|
||
- `create?` / `update?` / `destroy?` / `show?` / `edit?` / `reorder?` → 仅管理员
|
||
|
||
- **Enterprise::ArticlePolicy**(prepend):
|
||
- 所有 CRUD 操作增加 `knowledge_base_manage` 自定义权限检查
|
||
- 有 `knowledge_base_manage` 权限的自定义角色用户也可操作
|
||
|
||
---
|
||
|
||
## 10. AI辅助:Help Center 策展 + Article Writer(企业版)
|
||
|
||
### 功能描述
|
||
企业版提供 AI 辅助的帮助中心内容生成能力:
|
||
1. **Help Center 策展(Curation)**:给定一组 URL,AI 自动筛选出适合作为帮助中心文章的页面,生成分类+文章结构
|
||
2. **Article Writer**:给定 URL,AI 抓取网页内容并重写为帮助中心文章格式
|
||
3. **Onboarding 自动生成**:新用户注册后,系统可自动从其网站抓取内容生成帮助中心
|
||
|
||
### 涉及的业务逻辑
|
||
|
||
- **Captain::Llm::HelpCenterCurationService**:
|
||
- 输入:account + links(URL列表)
|
||
- 使用 GPT-4.1 模型,分析 URL 列表,筛选出适合做帮助中心文章的页面
|
||
- 输出:`{ categories: [...], articles: [...] }` 结构
|
||
|
||
- **Captain::Llm::ArticleWriterService**:
|
||
- 输入:account + 原始内容/URL
|
||
- 使用 LLM 将内容重写为帮助中心文章格式
|
||
|
||
- **Onboarding::HelpCenterArticleBuilder**(enterprise):
|
||
- 初始化参数:account, portal, user, article(含 urls, title, category_id)
|
||
- 流程:scrape → normalize → rewrite → 保存文章
|
||
- 抓取指定 URL 的页面内容,处理后生成帮助中心文章
|
||
|
||
- **Onboarding::HelpCenterArticleWriterJob** / **Onboarding::HelpCenterArticleGenerationJob**:
|
||
- 异步执行文章生成,失败时记录错误上下文
|
||
|
||
- **Captain::Tools::FaqLookupTool**(AI Copilot 工具):
|
||
- 输入:query(问题关键词)
|
||
- 使用语义搜索在已审批的 FAQ Responses 中查找匹配答案
|
||
- 返回格式化的问答内容,包含外部链接(如果是帮助中心文章则显示 Source)
|
||
|
||
---
|
||
|
||
## 模块间依赖关系
|
||
|
||
```
|
||
Account
|
||
└── Portal (1:N)
|
||
├── Category (1:N)
|
||
│ ├── Folder (1:N)
|
||
│ ├── RelatedCategory (M:N via related_categories)
|
||
│ ├── SubCategory (1:N, parent_category_id)
|
||
│ └── Article (1:N, category_id, nullable)
|
||
├── Article (1:N)
|
||
│ ├── ArticleEmbedding (1:N, enterprise)
|
||
│ └── AssociatedArticle (多语言关联链)
|
||
├── Channel::WebWidget (1:1, 可选绑定在线聊天)
|
||
└── Inbox (1:N, nullable)
|
||
```
|
||
|
||
### 关键配置/功能开关
|
||
- `help_center_embedding_search` — 开启向量语义搜索(企业版)
|
||
- `captain_tasks` — 开启 AI 翻译/写作能力(企业版)
|
||
- Portal.config(jsonb):`allowed_locales`, `default_locale`, `draft_locales`, `layout`, `social_profiles`
|
||
- Portal.ssl_settings(jsonb):自定义域名 SSL 配置
|
||
|
||
### 技术要点
|
||
1. **PgSearch 全文搜索**:tsearch prefix 模式 + 权重分级(A/B/C) + normalization=2
|
||
2. **pgvector 向量搜索**:ivfflat 索引 + cosine 距离 + 1536维 OpenAI embedding
|
||
3. **多语言架构**:Portal.allowed_locales → Category.locale → Article.locale + associated_article_id 关联链
|
||
4. **Tracking Pixel**:1x1 PNG + 24h private cache + 浏览量计数
|
||
5. **Layout 模式**:classic(传统) / documentation(文档风格),通过 request.variant 区分视图模板
|
||
6. **自定义域名 + SSL**:DNS CNAME 指向 + 自动 SSL 证书管理
|
||
7. **AI Integration**:GPT-4.1 策展 + Article Writer + Embedding Service + Translation Service |