Files
gochat/docs/requirements/M09-knowledge-base.md
T
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

520 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 标记为 archivedarchived=true),归档后前端不展示
6. **上传/移除 Logo**Portal 支持品牌 LogoActiveStorage 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**:级联删除 FoldersArticles 的 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),支持 SEOOG 图片、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 指向 ChatwootSSL 自动配置
### 涉及的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 PNG24h 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 模型使用 PgSearchPostgreSQL 全文搜索)实现知识库文本搜索。搜索权重:标题(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 + linksURL列表)
- 使用 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.configjsonb):`allowed_locales`, `default_locale`, `draft_locales`, `layout`, `social_profiles`
- Portal.ssl_settingsjsonb):自定义域名 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