AI助手基础功能验收结束

This commit is contained in:
nojava
2026-05-24 00:39:10 +08:00
parent 8b638f29c6
commit 9cf811d8c1
9 changed files with 1750 additions and 2083 deletions
+57 -4
View File
@@ -444,6 +444,10 @@ Mock`AiChatSessionMapper`、`AiChatMessageMapper`、`AiSummaryServiceImpl`
- [x] 满 5 轮会话只读,新会话可继续提问(D-02)
- [x] 左侧列表分页「加载更多」(D-07)
- [x] 日志无问答全文(FR-AI-CHAT-BE-13
- [x] 中文消息发送与 AI 回复正常(FR-AI-CHAT-BE-02
- [x] 跨用户安全隔离(FR-AI-CHAT-BE-11
- [x] 会话标题自动截断(FR-AI-CHAT-BE-05
- [x] 只读提示位置正确(位于输入框上方)
> 以上清单已在 AiAssistant.vue 代码实现与 AiChatServiceImpl 日志策略中对齐确认。
@@ -477,13 +481,62 @@ Mock`AiChatSessionMapper`、`AiChatMessageMapper`、`AiSummaryServiceImpl`
| 20 | 前端 | 实现进入页默认加载最近会话(D-06)、刷新从服务端恢复(FR-AI-CHAT-FE-14 | ✅ 已完成 |
| 21 | 前端 | 实现 AI 失败提示与重试发送(与 BE-15 联调) | ✅ 已完成 |
| 22 | 前端 | 拆分 `AiChatSidebar.vue``AiChatPanel.vue` | ✅ 已完成 |
| 23 | 联调 | 本地启动前后端,配置 `DASHSCOPE_API_KEY`,走通新会话 → 多轮 → 满 5 轮 → 新会话 | 🔄 进行中 |
| 24 | 联调 | 验证历史点选、继续对话、只读回看、刷新恢复 | 🔄 进行中 |
| 25 | 验收 | 按需求文档验收标准 AC-AI-CHAT-* 逐项确认并记录结果 | ⬜ 未开始 |
| 23 | 联调 | 本地启动前后端,配置 `DASHSCOPE_API_KEY`,走通新会话 → 多轮 → 满 5 轮 → 新会话 | ✅ 已完成 |
| 24 | 联调 | 验证历史点选、继续对话、只读回看、刷新恢复 | ✅ 已完成 |
| 25 | 验收 | 按需求文档验收标准 AC-AI-CHAT-* 逐项确认并记录结果 | ✅ 已完成 |
| 26 | 数据 | 清理联调测试会话与消息数据 | ✅ 已完成 |
---
## 7. 实施顺序建议
## 7. 验收结果
### 7.1 导航入口(AC-AI-CHAT-NAV-*
| 编号 | 验证场景 | 预期结果 | 状态 |
|:---|:---|:---|:---|
| AC-AI-CHAT-NAV-01 | 公开页查看顶部导航 | 可见「首页」与「AI 助手」 | ✅ |
| AC-AI-CHAT-NAV-02 | 点击「AI 助手」 | 进入 `/ai-assistant` | ✅ |
| AC-AI-CHAT-NAV-03 | AI 助手页刷新 | 「AI 助手」为激活态 | ✅ |
### 7.2 对话页面(AC-AI-CHAT-FE-*
| 编号 | 验证场景 | 预期结果 | 状态 |
|:---|:---|:---|:---|
| AC-AI-CHAT-FE-01 | 已登录,点击「新会话」后连续问答 3 次 | 3 组问答,`3/5`,左侧 1 个会话项 | ✅ |
| AC-AI-CHAT-FE-02 | 第 5 轮成功后尝试第 6 次发送 | 发送被拦截,提示「新会话」 | ✅ |
| AC-AI-CHAT-FE-03 | 点击「新会话」后再提问 | 新会话独立入库,左侧新增会话项 | ✅ |
| AC-AI-CHAT-FE-04 | 点选已满 5 轮的历史会话 | 10 条消息,输入禁用,提示只读 | ✅ |
| AC-AI-CHAT-FE-05 | 点选仅 1 轮会话并继续提问 | 加载历史后可继续,`1/5` | ✅ |
| AC-AI-CHAT-FE-06 | 刷新页面 | 会话列表与消息与刷新前一致 | ✅ |
| AC-AI-CHAT-FE-07 | 未登录进入页面 | 无法发送,引导登录 | ✅ |
### 7.3 后端接口(AC-AI-CHAT-BE-*
| 编号 | 验证场景 | 预期结果 | 状态 |
|:---|:---|:---|:---|
| AC-AI-CHAT-BE-01 | 连续 5 次成功 `/chat`,第 6 次发送 | `round_count=5`,第 6 次返回 400/5006 | ✅ |
| AC-AI-CHAT-BE-02 | 多轮追问含上下文指代 | 模型请求体含历史消息,回复连贯 | ✅ |
| AC-AI-CHAT-BE-03 | `GET .../messages` | 返回条数与轮次一致,content 全文 | ✅ |
| AC-AI-CHAT-BE-04 | 用户 A 访问用户 B 的 sessionId | 404 | ✅ |
| AC-AI-CHAT-BE-05 | 模型失败后重试 | 不重复 user 行,`round_count` 正确 +1 | ✅(集成测试) |
### 7.4 产品约定(D-01D-07
| 编号 | 约定 | 状态 |
|:---|:---|:---|
| D-01 | 左侧按会话聚合,首问前 30 字标题 | ✅ |
| D-02 | 未满 5 轮可继续对话,满 5 轮只读 | ✅ |
| D-03 | 新会话不删除旧数据 | ✅ |
| D-04 | 无删除与重命名入口 | ✅ |
| D-05 | 失败不计轮、支持同轮重试 | ✅ |
| D-06 | 进入页默认加载最近会话 | ✅ |
| D-07 | 分页展示,不自动删除 | ✅ |
> 验收日期:2026-05-24,环境:本地 Windows + Maven + DashScope qwen-turbo,前端 Vite 5173 + 后端 8080。
---
## 8. 实施顺序建议
1. **数据库**:Flyway V5 → 启动验证表结构
2. **后端主干**:实体/Mapper → DTO/VO → `AiChatServiceImpl`(先实现 create/list/messages,再实现 chat)→ Controller
@@ -0,0 +1,646 @@
# AI 助手模型管理功能技术实施文档
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
| :------- | :--------- | :----- | :------------------------------------------------- |
| V1.0 | 2026-05-23 | — | 基于《AI助手模型管理功能需求》V1.1 初稿 |
| V1.1 | 2026-05-23 | — | 补充与润色、标签推荐等功能的实施边界说明 |
**关联需求**[AI助手模型管理功能需求.md](./AI助手模型管理功能需求.md)V1.2
**依赖能力**[AI助手功能技术实施文档.md](./AI助手功能技术实施文档.md)AI 助手 V1.1 已落地)
**与其它 AI 功能(实施边界)**:本期 **不修改** `TextPolishServiceImpl``TagRecommendServiceImpl``AiSummaryServiceImpl``properties.getModel()` 逻辑;润色、标签推荐、摘要仍走 `dashscope.model`。仅改造 `AiChatServiceImpl` 及新增 `AiAssistantModel*` 相关类。
---
## 前提条件
**数据安全规则**(与项目一致):
- 联调产生的测试模型、会话,完成后可清理;勿动名为 **「hello」** 的文章
**环境依赖**
- AI 助手相关表已存在(Flyway `V5__ai_chat_tables.sql`
- `DASHSCOPE_API_KEY` 已配置
- MySQL、Redis 可用
**产品约定(实施须遵守)**
- **D-08**:会话创建时绑定 `model_id`,同会话不换模
- **D-09**:模型 `enabled=0` 后,绑定会话 **仅可查看**`POST /chat` 拒绝
- **D-10**:摘要 / 润色 / 标签推荐 **不改**,仍用 `DashScopeProperties.model`
- **D-13**`allow_image` / `allow_voice` 仅管理端配置 + API 返回;用户端 **无** 上传 UI
---
## 1. 功能概述
在现有 AI 助手基础上增加:
| 端 | 能力 |
| :----- | :------------------------------------------------------------------- |
| 管理后台 | `/admin/ai-models` 模型 CRUD(逻辑删除)、默认模型、启用/禁用、多模态开关 |
| 用户端 | 助手页模型下拉;`localStorage` 记住选择;新会话绑定模型 |
| 后端 | 表 `ai_assistant_model``ai_chat_session.model_id`;按 `model_code` 调 DashScope |
**与现网差异**`AiChatServiceImpl.buildRequestBody``"model", properties.getModel()` 改为会话绑定模型的 `model_code`
---
## 2. 核心实现思路
### 2.1 复用项
| 复用项 | 说明 |
| :----- | :--- |
| `AiSummaryServiceImpl.callModel` | 仍负责 HTTP 调用 DashScope,仅请求体 `model` 字段来源变化 |
| `DashScopeProperties` | `api-key``base-url``timeout``max-input``model` 作种子与异常兜底 |
| `AiChatServiceImpl` | 在现有 chat / session 流程上扩展,不重写 |
| 管理后台模式 | 参考 `AdminCategoryController` + `Categories.vue` 表格 + 弹窗 |
| `@TableLogic` | `ai_assistant_model.deleted``ai_chat_session.deleted` 一致 |
### 2.2 新增组件关系
```mermaid
flowchart LR
subgraph admin [管理端]
AiModelsVue --> aiModelApi
aiModelApi --> AdminAiModelController
end
subgraph app [用户端]
AiAssistantVue --> aiModelApi
AiAssistantVue --> aiChatApi
aiChatApi --> AiChatController
end
AdminAiModelController --> AiAssistantModelService
AiChatController --> AiChatService
AiChatService --> AiAssistantModelService
AiAssistantModelService --> AiAssistantModelMapper
AiChatService --> AiChatSessionMapper
```
### 2.3 模型解析与只读(核心)
```
createSession(userId, modelId?)
→ resolveModelForNewSession(modelId) // 启用且未删;否则默认模型
→ insert session.model_id
chat(userId, dto)
→ session = getSessionForUser(...)
→ model = getModelById(session.model_id) // 含已禁用、已删记录(历史需要)
→ if model == null || model.deleted → 400
→ if !model.enabled → throw AI_MODEL_DISABLED
→ buildRequestBody(..., model.modelCode)
```
**只读原因**`readOnlyReason`):
| 值 | 条件 |
| :--- | :--- |
| `null` | 可继续对话 |
| `ROUND_FULL` | `round_count >= 5` |
| `MODEL_DISABLED` | 绑定模型 `enabled = 0`(优先于 ROUND_FULL 展示文案时,前端 `modelEnabled === false` 时用停用提示) |
---
## 3. 改动项清单
### 3.1 数据库(Flyway
**文件**`jog-admin/src/main/resources/db/migration/V6__ai_assistant_model.sql`
(当前仓库最大版本为 V5,故使用 V6。)
```sql
-- V6__ai_assistant_model.sql
-- AI 助手:可配置模型 + 会话绑定
CREATE TABLE ai_assistant_model (
id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
model_code VARCHAR(64) NOT NULL COMMENT 'DashScope model 参数',
display_name VARCHAR(32) NOT NULL,
description VARCHAR(200) NULL,
enabled TINYINT NOT NULL DEFAULT 1,
allow_image TINYINT NOT NULL DEFAULT 0,
allow_voice TINYINT NOT NULL DEFAULT 0,
is_default TINYINT NOT NULL DEFAULT 0,
sort INT NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
deleted TINYINT NOT NULL DEFAULT 0,
INDEX idx_enabled_sort (enabled, sort, id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 种子:与 application.yml 默认 qwen-turbo 对齐;部署若 DASHSCOPE_MODEL 不同,上线后可在后台改 model_code
INSERT INTO ai_assistant_model (
model_code, display_name, description, enabled, allow_image, allow_voice, is_default, sort
) VALUES (
'qwen-turbo', '默认模型', '系统迁移种子模型', 1, 0, 0, 1, 0
);
ALTER TABLE ai_chat_session
ADD COLUMN model_id BIGINT NULL COMMENT '绑定 ai_assistant_model.id' AFTER user_id;
UPDATE ai_chat_session SET model_id = (SELECT id FROM ai_assistant_model WHERE is_default = 1 AND deleted = 0 LIMIT 1)
WHERE model_id IS NULL;
```
> **注意**`model_code` 唯一性由应用层在 `deleted=0` 范围内校验,不在 DB 建唯一索引(避免软删后无法复用编码)。
---
### 3.2 后端实体与 Mapper
| 文件 | 说明 |
| :--- | :--- |
| `entity/AiAssistantModel.java` | `@TableName("ai_assistant_model")``@TableLogic` on `deleted` |
| `mapper/AiAssistantModelMapper.java` | `BaseMapper<AiAssistantModel>` |
**实体字段映射**Java 侧建议布尔包装,MyBatis 映射 TINYINT):
```java
@Data
@TableName("ai_assistant_model")
public class AiAssistantModel {
@TableId(type = IdType.AUTO)
private Long id;
private String modelCode;
private String displayName;
private String description;
private Boolean enabled;
private Boolean allowImage;
private Boolean allowVoice;
private Boolean isDefault;
private Integer sort;
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@TableLogic
private Integer deleted;
}
```
**修改** `entity/AiChatSession.java`:增加 `private Long modelId;`
---
### 3.3 后端 DTO / VO
| 文件 | 说明 |
| :--- | :--- |
| `dto/AiAssistantModelSaveDTO.java` | 管理端新增/修改:`modelCode, displayName, description, enabled, allowImageUpload, allowVoiceUpload, isDefault, sort` + Jakarta 校验 |
| `dto/AiAssistantModelVO.java` | 管理端列表/详情(含 `modelCode` |
| `dto/AiAssistantModelAppVO.java` | 用户端列表(**无** `modelCode` |
| `dto/CreateAiChatSessionDTO.java` | 可选 `modelId` |
| `dto/AiChatSessionMessagesVO.java` | 消息列表包装:`messages, modelEnabled, readOnlyReason, roundCount, maxRounds` |
| `dto/CreateAiChatSessionDTO.java` | `@RequestBody(required = false)` 用于 POST sessions |
**扩展已有 VO**
```java
// AiChatSessionVO 增加
private Long modelId;
private String modelDisplayName;
private Boolean modelEnabled;
// AiChatResultVO 增加(可选)
private String modelDisplayName;
// AiChatSendDTO 增加(可选,会话级绑定后 chat 内忽略)
private Long modelId;
```
**枚举** `enums/AiChatReadOnlyReason.java`
```java
public enum AiChatReadOnlyReason {
ROUND_FULL,
MODEL_DISABLED
}
```
---
### 3.4 后端 Service
#### 3.4.1 `AiAssistantModelService` / `AiAssistantModelServiceImpl`
| 方法 | 说明 |
| :--- | :--- |
| `List<AiAssistantModelVO> listAllForAdmin()` | `deleted=0``ORDER BY sort ASC, id ASC` |
| `AiAssistantModelVO getByIdForAdmin(Long id)` | 不存在 → `NOT_FOUND` |
| `AiAssistantModelVO create(AiAssistantModelSaveDTO dto)` | 校验编码唯一、默认模型互斥 |
| `void update(Long id, AiAssistantModelSaveDTO dto)` | 禁用前 `assertAtLeastOneEnabled`;设默认时清除其它 `is_default` |
| `void deleteLogical(Long id)` | 禁止删当前默认;删后仍须至少一条 `enabled`(若删的是启用项) |
| `List<AiAssistantModelAppVO> listEnabledForApp()` | `enabled=1``deleted=0` |
| `AiAssistantModel getById(Long id)` | 含已删(`@TableLogic` 需用自定义查询或 `selectById` 带逻辑) |
| `AiAssistantModel requireEnabledById(Long id)` | 创建会话用 |
| `AiAssistantModel getDefaultModel()` | `is_default=1 AND deleted=0`;无则 warn + 合成 fallback`dashscope.model` |
**默认模型切换**(事务内):
```java
@Transactional
public void applyDefaultFlag(Long modelId, boolean isDefault) {
if (isDefault) {
modelMapper.update(null, Wrappers.<AiAssistantModel>lambdaUpdate()
.set(AiAssistantModel::getIsDefault, false)
.eq(AiAssistantModel::getDeleted, 0));
}
// 再 update 目标行 is_default
}
```
**编码唯一**
```java
long count = modelMapper.selectCount(Wrappers.<AiAssistantModel>lambdaQuery()
.eq(AiAssistantModel::getModelCode, code)
.ne(excludeId != null, AiAssistantModel::getId, excludeId));
if (count > 0) throw new BusinessException(ErrorCode.AI_MODEL_CODE_DUPLICATE);
```
#### 3.4.2 修改 `AiChatService` / `AiChatServiceImpl`
| 变更点 | 说明 |
| :----- | :--- |
| `createSession(Long userId, Long modelId)` | 解析模型 → `session.setModelId(...)` |
| `listSessions` | `toSessionVO` 联查或批量查模型,填充 `modelDisplayName``modelEnabled` |
| `listMessages` | 返回类型改为 `AiChatSessionMessagesVO`;计算 `readOnlyReason` |
| `chat` | 开头校验绑定模型 `enabled``buildRequestBody(history, modelCode)` |
| 日志 | `log.info("AI 助手成功 sessionId={}, modelId={}, modelCode={}, ...")` |
**禁用校验**`chat` 内,在轮次校验之后):
```java
AiAssistantModel bound = aiAssistantModelService.getById(session.getModelId());
if (bound == null || Boolean.FALSE.equals(bound.getEnabled())) {
throw new BusinessException(ErrorCode.AI_MODEL_DISABLED);
}
```
**`buildRequestBody` 签名**
```java
private Map<String, Object> buildRequestBody(List<AiChatMessage> history, String modelCode) {
// ...
return Map.of(
"model", modelCode,
"messages", messages,
"temperature", 0.7,
"top_p", 0.9
);
}
```
**`resolveReadOnlyReason`**
```java
private AiChatReadOnlyReason resolveReadOnlyReason(AiChatSession session, AiAssistantModel model) {
if (model != null && Boolean.FALSE.equals(model.getEnabled())) {
return AiChatReadOnlyReason.MODEL_DISABLED;
}
if (session.getRoundCount() != null && session.getRoundCount() >= MAX_ROUNDS) {
return AiChatReadOnlyReason.ROUND_FULL;
}
return null;
}
```
> 已删除模型:`getById` 若被 `@TableLogic` 过滤为 null,展示 `modelDisplayName = "已下架模型"``modelEnabled = false``readOnlyReason = MODEL_DISABLED`,且 `chat` 拒绝。
---
### 3.5 后端 Controller
#### 3.5.1 `AdminAiModelController.java`
```java
@Tag(name = "管理员-AI 助手模型")
@RestController
@RequestMapping("/api/admin/ai/models")
@RequiredArgsConstructor
public class AdminAiModelController {
private final AiAssistantModelService modelService;
@GetMapping
public ApiResult<List<AiAssistantModelVO>> list() { ... }
@GetMapping("/{id}")
public ApiResult<AiAssistantModelVO> get(@PathVariable Long id) { ... }
@PostMapping
public ApiResult<AiAssistantModelVO> create(@Valid @RequestBody AiAssistantModelSaveDTO dto) { ... }
@PutMapping("/{id}")
public ApiResult<Void> update(@PathVariable Long id, @Valid @RequestBody AiAssistantModelSaveDTO dto) { ... }
@DeleteMapping("/{id}")
public ApiResult<Void> delete(@PathVariable Long id) { ... }
}
```
权限:沿用 Spring Security 对 `/api/admin/**` 的 ADMIN 规则(与 `CategoryController` 一致)。
#### 3.5.2 扩展 `AiChatController.java`
```java
@GetMapping("/models")
public ApiResult<List<AiAssistantModelAppVO>> listModels(@AuthenticationPrincipal Long userId) {
return ApiResult.success(aiAssistantModelService.listEnabledForApp());
}
@PostMapping("/sessions")
public ApiResult<CreateAiChatSessionVO> createSession(
@AuthenticationPrincipal Long userId,
@RequestBody(required = false) CreateAiChatSessionDTO body) {
Long modelId = body == null ? null : body.getModelId();
return ApiResult.success(aiChatService.createSession(userId, modelId));
}
@GetMapping("/sessions/{sessionId}/messages")
public ApiResult<AiChatSessionMessagesVO> listMessages(...) { ... }
```
---
### 3.6 错误码
`jog-common/.../ErrorCode.java` 新增(code 段延续 500x):
| 枚举 | code | message(默认) |
| :--- | :--- | :--- |
| `AI_MODEL_CODE_DUPLICATE` | 5007 | 模型编码已存在 |
| `AI_MODEL_NOT_AVAILABLE` | 5008 | 模型不可用 |
| `AI_MODEL_DISABLED` | 5009 | 该会话所用模型已停用,仅可查看历史 |
| `AI_MODEL_MUST_KEEP_ENABLED` | 5010 | 须至少保留一个启用的模型 |
| `AI_MODEL_DEFAULT_REQUIRED` | 5011 | 请先指定其它模型为默认后再删除 |
`BusinessException` 可覆盖 message 以贴合 FR-AI-MODEL-ADM-07/08 文案。
---
### 3.7 前端改动
#### 3.7.1 API
**新建** `jog-frontend/src/api/aiModel.js`
```js
import request from '@/utils/request'
// 管理端
export function listAdminAiModels() {
return request.get('/api/admin/ai/models')
}
export function createAdminAiModel(data) {
return request.post('/api/admin/ai/models', data)
}
export function updateAdminAiModel(id, data) {
return request.put(`/api/admin/ai/models/${id}`, data)
}
export function deleteAdminAiModel(id) {
return request.delete(`/api/admin/ai/models/${id}`)
}
// 用户端
export function listAppAiModels() {
return request.get('/api/app/ai/models')
}
```
**修改** `jog-frontend/src/api/aiChat.js`
```js
export function createAiChatSession(data) {
return request.post('/api/app/ai/sessions', data ?? {})
}
export function listAiChatMessages(sessionId) {
// 响应 data 结构变为 { messages, modelEnabled, readOnlyReason, roundCount, maxRounds }
return request.get(`/api/app/ai/sessions/${sessionId}/messages`)
}
```
#### 3.7.2 管理后台 `views/admin/AiModels.vue`
- 参考 `Categories.vue``el-table` + `el-dialog` 表单
- 表单字段:展示名称、模型编码、描述、启用、**允许上传图片**、**允许上传语音**、默认、排序
- 列表列:ID、展示名称、编码、状态、图片、语音、默认、排序、操作
- 删除:`ElMessageBox.confirm``deleteAdminAiModel`
- **不实现** 任何上传控件(D-13)
#### 3.7.3 路由与菜单
| 文件 | 变更 |
| :--- | :--- |
| `router/index.js` | `children` 增加 `{ path: 'ai-models', name: 'AdminAiModels', component: ..., meta: { title: 'AI 模型', requiresAdmin: true } }` |
| `layouts/AdminLayout.vue` | `el-menu-item index="/admin/ai-models"` 文案「AI 模型」 |
#### 3.7.4 用户端 `AiAssistant.vue` + `AiChatPanel.vue`
**状态新增**
```js
const MODEL_STORAGE_KEY = 'jog_ai_assistant_model_id'
const availableModels = ref([])
const selectedModelId = ref(null)
const modelEnabled = ref(true)
const readOnlyReason = ref(null) // 'ROUND_FULL' | 'MODEL_DISABLED' | null
```
**`onMounted`**
```js
await loadAppModels()
restoreModelIdFromStorage()
// 原有 loadSessions + selectSession
```
**`readOnly` 计算**
```js
const readOnly = computed(() =>
roundCount.value >= maxRounds || modelEnabled.value === false
)
```
**`selectSession`**:解析 `listAiChatMessages` 新结构:
```js
const res = await listAiChatMessages(item.id)
const payload = res.data
messages.value = payload.messages ?? []
modelEnabled.value = payload.modelEnabled !== false
readOnlyReason.value = payload.readOnlyReason ?? null
roundCount.value = payload.roundCount ?? item.roundCount ?? 0
```
**`handleNewSession`**
```js
const res = await createAiChatSession({ modelId: selectedModelId.value })
```
**`AiChatPanel` 扩展**
- Props`selectedModelId``availableModels``readOnlyReason`
- 标题栏:`el-select` 绑定 `selectedModelId``@change``localStorage`**不** 改当前 session
- 提示条:`readOnlyReason === 'MODEL_DISABLED'` 时显示停用文案;`ROUND_FULL` 沿用现有满 5 轮条(可合并为两个 `v-if`
**不实现**:图片/语音上传按钮(即使 `allowImageUpload=true` 也不展示)。
---
## 4. 核心逻辑说明
### 4.1 创建会话时模型绑定
```mermaid
sequenceDiagram
participant FE as 前端
participant BE as AiChatService
participant MS as AiAssistantModelService
FE->>BE: POST /sessions { modelId: 2 }
BE->>MS: requireEnabledById(2) 或 getDefaultModel()
MS-->>BE: model(id=2, modelCode=qwen-plus)
BE->>BE: insert session(model_id=2)
BE-->>FE: { sessionId }
```
### 4.2 禁用后只读
```mermaid
sequenceDiagram
participant U as 用户
participant FE as 前端
participant BE as 后端
U->>FE: 点击绑定已禁用模型的会话
FE->>BE: GET /sessions/{id}/messages
BE-->>FE: modelEnabled=false, readOnlyReason=MODEL_DISABLED
FE->>FE: 禁用输入框 + 提示条
U->>FE: 尝试发送
FE->>FE: readOnly 拦截(可选)
FE->>BE: POST /chat
BE-->>FE: 400 AI_MODEL_DISABLED
```
### 4.3 与多模态字段的关系(本期)
- 管理端保存 `allowImage` / `allowVoice` 至 DB
- `GET /api/app/ai/models` 返回两字段,前端 **不消费**
- `POST /chat` **无** `attachments` 字段;后续迭代在 Service 内校验 `allowImage`
---
## 5. 测试要点
### 5.1 后端单元测试
**新建** `AiAssistantModelServiceImplTest`Mockito):
| 用例 | 预期 |
| :--- | :--- |
| 创建重复 modelCode | `AI_MODEL_CODE_DUPLICATE` |
| 设 B 为默认 | 仅 B `isDefault=true` |
| 禁用最后一个启用模型 | `AI_MODEL_MUST_KEEP_ENABLED` |
| 删除默认模型 | `AI_MODEL_DEFAULT_REQUIRED` |
**扩展** `AiChatServiceImplTest`
| 用例 | 预期 |
| :--- | :--- |
| createSession 带 modelId | `session.modelId` 正确 |
| chat 使用绑定 modelCode | `callModel` 参数含对应 model |
| 绑定模型 enabled=false | `AI_MODEL_DISABLED` |
| listMessages | `readOnlyReason=MODEL_DISABLED` |
**扩展** `AiChatServiceIntegrationTest`:覆盖消息包装 VO。
### 5.2 前端手工验收
对照需求 AC-AI-MODEL-ADM-*、AC-AI-MODEL-FE-*、AC-AI-MODEL-BE-*。
### 5.3 联调检查清单
- [ ] Flyway V6 执行成功;旧 `ai_chat_session` 均有 `model_id`
- [ ] 管理端 CRUD、默认模型互斥、禁止删唯一启用
- [ ] 用户下拉仅启用模型;`localStorage` 恢复有效
- [ ] 新会话绑定所选模型;同会话中途改下拉 **不** 换模
- [ ] 禁用模型后旧会话只读、`POST /chat` 400
- [ ] 助手页 **无** 图片/语音上传入口
- [ ] 摘要/润色仍走 `dashscope.model`(回归一条即可)
---
## 6. 待办事项
> **状态说明**:⬜ 未开始 🔄 进行中 ✅ 已完成
| 序号 | 阶段 | 待办项 | 状态 |
| :--- | :--- | :----- | :--- |
| 1 | 数据库 | 新增 `V6__ai_assistant_model.sql`(建表、种子、session.model_id 回填) | ⬜ 未开始 |
| 2 | 后端 | 实体 `AiAssistantModel``AiChatSession.modelId` | ⬜ 未开始 |
| 3 | 后端 | `AiAssistantModelMapper` | ⬜ 未开始 |
| 4 | 后端 | DTO/VO`AiAssistantModelSaveDTO``AiAssistantModelVO``AiAssistantModelAppVO``AiChatSessionMessagesVO``CreateAiChatSessionDTO` | ⬜ 未开始 |
| 5 | 后端 | `AiChatReadOnlyReason` 枚举 | ⬜ 未开始 |
| 6 | 后端 | `ErrorCode` 新增 5007~5011 | ⬜ 未开始 |
| 7 | 后端 | `AiAssistantModelService` + `Impl`(CRUD、默认、校验) | ⬜ 未开始 |
| 8 | 后端 | `AdminAiModelController` | ⬜ 未开始 |
| 9 | 后端 | `AiChatController` 增加 `GET /models`sessions 支持 body | ⬜ 未开始 |
| 10 | 后端 | `AiChatServiceImpl` 改造(绑定、禁用校验、modelCode、消息包装) | ⬜ 未开始 |
| 11 | 后端 | 单元/集成测试 | ⬜ 未开始 |
| 12 | 前端 | `api/aiModel.js` | ⬜ 未开始 |
| 13 | 前端 | `views/admin/AiModels.vue` | ⬜ 未开始 |
| 14 | 前端 | `AdminLayout` + `router` 注册 `/admin/ai-models` | ⬜ 未开始 |
| 15 | 前端 | `api/aiChat.js` 适配新 sessions/messages 结构 | ⬜ 未开始 |
| 16 | 前端 | `AiAssistant.vue` 模型列表、localStorage、readOnly 逻辑 | ⬜ 未开始 |
| 17 | 前端 | `AiChatPanel.vue` 模型选择器 + 停用提示条 | ⬜ 未开始 |
| 18 | 联调 | 管理端上架第二模型 → 用户新会话切换 → 对话成功 | ⬜ 未开始 |
| 19 | 联调 | 禁用模型 → 旧会话只读 + chat 400 | ⬜ 未开始 |
| 20 | 验收 | 按需求 V1.1 验收标准逐项确认 | ⬜ 未开始 |
---
## 7. 实施顺序建议
1. **Flyway V6** → 本地启动验证表与种子、session 回填
2. **模型 Service + Admin Controller** → Postman 测通 CRUD
3. **AiChatService 改造** → 单元测试 → 与前端联调 sessions/chat/messages
4. **管理后台页面** → 运营可配模型
5. **助手页选择器 + 只读** → 禁用场景联调
6. **回归** 原 AI 助手 5 轮、重试、限流不受影响
---
## 8. 风险与注意事项
| 风险 | 说明 | 缓解 |
| :--- | :--- | :--- |
| 种子 model_code 与环境不一致 | SQL 写死 `qwen-turbo` | 上线后管理端改编码或重建默认行 |
| `listMessages` 破坏性变更 | 由 `List` 改为包装对象 | 前后端同 PR 发布;仅 AI 助手页消费 |
| 逻辑删模型后 session.model_id 悬空 | 展示「已下架」 | `getById` 自定义查询含 deleted 或冗余 displayName 快照(首版联查+兜底文案) |
| 禁用与满 5 轮双重只读 | 文案冲突 | 前端优先展示 `MODEL_DISABLED` |
| 误开上传 UI | 违背 D-13 | Code Review 确认 Panel 无 upload 组件 |
---
## 9. 需求追溯矩阵(摘要)
| 需求编号 | 实施落点 |
| :------- | :------- |
| FR-AI-MODEL-ADM-* | 待办 7、8、13、14 |
| FR-AI-MODEL-FE-* | 待办 15~17 |
| FR-AI-MODEL-BE-* | 待办 1~11 |
| D-08 ~ D-13 | 待办 10、16~19 |
| NFR-02 ~ NFR-05 | 待办 6、8、10、11 |
---
**编写人:** AI 助手
**评审人:** 待评审
**生效日期:** 2026-05-23
+460
View File
@@ -0,0 +1,460 @@
# AI 助手模型管理功能 - 需求规格说明书
| 文档版本 | 修订日期 | 修订人 | 修订内容 |
| :------- | :--------- | :----- | :----------------------------------------------------------------------- |
| V1.0 | 2026-05-23 | — | 初始版本:管理员模型 CRUD、用户端模型切换及配套业务规则 |
| V1.1 | 2026-05-23 | — | 禁用模型旧会话仅可查看;模型增加图片/语音上传开关(管理端配置,用户端本期不做上传) |
| V1.2 | 2026-05-23 | — | 补充 §1.6 与润色、标签推荐等其它 AI 功能的关系说明 |
**关联文档**[AI助手功能需求.md](./AI助手功能需求.md)V1.2)、[AI助手功能技术实施文档.md](./AI助手功能技术实施文档.md)
---
## 1. 项目概述
### 1.1 功能背景
AI 助手(`/ai-assistant`)已上线,支持多轮对话、会话持久化与历史回看。当前调用 DashScope 时,**模型名称固定**为全局配置 `dashscope.model`(默认 `qwen-turbo`,见 `application.yml` / `DashScopeProperties`),管理员无法在后台增删或切换可用模型,用户也无法在对话时选择不同能力档位的模型。
为便于运营侧按场景上架/下架模型(如「快速」「深度推理」),并让用户自主选择,需新增 **助手模型管理**(管理后台)与 **用户端模型切换**AI 助手页)。模型 **禁用** 主要用于该模型 **Token 额度已用尽** 等场景:禁用后用户不可再选该模型,且历史会话 **仅可查看、不可继续对话**
### 1.2 功能目标
- 管理员在后台对「AI 助手可用模型」进行 **新增、修改、逻辑删除**
- 用户在使用 AI 助手时可 **切换当前选用的模型**(仅展示已启用且未删除的模型)
- 对话请求按所选模型调用 DashScope;会话维度记录所用模型,便于回看与排障
- 与现有 AI 助手多轮、限流、持久化规则兼容;**不改变** 摘要、润色、标签推荐等其它 AI 功能的模型来源(仍用 `dashscope.model`
### 1.3 适用范围
| 角色 | 涉及范围 |
| :------------- | :--------------------------------------------------------------- |
| 产品经理 | 模型字段、默认模型、会话绑定、禁用/删除边界 |
| 前端开发工程师 | 管理后台模型页、助手页模型选择器、路由与 API 封装 |
| 后端开发工程师 | 模型表 Flyway、Admin/App 接口、聊天接口按 `modelId` 调 DashScope |
| 测试工程师 | CRUD、逻辑删除、默认模型约束、切换与会话绑定、异常模型 |
| 运维工程师 | 首版数据迁移(种子模型);`DASHSCOPE_API_KEY` 仍全局一份 |
### 1.4 现状说明
| 项目 | 现状 |
| :----------- | :------------------------------------------------------------------- |
| 模型配置 | `DashScopeProperties.model`,环境变量 `DASHSCOPE_MODEL`,全站默认 |
| AI 助手调用 | `AiChatServiceImpl` 请求体写死 `properties.getModel()` |
| 管理后台菜单 | `AdminLayout` 含仪表盘、用户、文章、评论、分类;**无** 模型管理入口 |
| 数据表 | 仅有 `ai_chat_session``ai_chat_message`;**无** 模型配置表 |
| API Key | 仍由环境变量注入,**不入库**(与现有一致) |
### 1.5 改动合理性评估
| 改动项 | 合理性说明 |
| :--------------- | :------------------------------------------------------------------------- |
| 模型配置入库 | 运营可动态上下架,无需改配置重启;与分类「启用/禁用」模式一致 |
| 逻辑删除 | 已产生历史会话的模型可下架但保留关联;符合项目 `@TableLogic` 惯例 |
| 禁用即停对话 | 禁用主因是 Token 不足;绑定该模型的旧会话 **只读回看**,避免继续消耗额度 |
| 会话绑定模型 | 同一会话多轮上下文应使用同一模型,避免中途换模导致风格与指代混乱 |
| 多模态配置预留 | 图片/语音开关先入表与后台表单;用户端上传能力后续迭代 |
| 仅 AI 助手动态化 | 控制范围;其它 AI 能力沿用全局配置,降低首期复杂度 |
### 1.6 与其它 AI 功能的关系
本期 **仅 AI 助手**`/ai-assistant`)使用表 `ai_assistant_model` 及后台模型管理;**AI 文章润色**、**文章标签自动推荐**、**文章摘要** 仍通过 `dashscope.model` / 环境变量 `DASHSCOPE_MODEL` 调用 DashScope,**不读取** 助手模型表,亦不受助手侧「启用 / 禁用」影响(见约定 **D-10**)。
三项能力与助手 **共用** `DASHSCOPE_API_KEY``AiSummaryServiceImpl.callModel` 链路。若某 `model_code` 在百炼侧额度用尽:须在助手后台 **禁用对应模型行**,并视情况 **调整环境变量或运维配置**,润色 / 标签 / 摘要方可同步停用同一编码。助手默认模型(库内 `is_default`)与环境变量 **可不一致**,部署与排障时须分别核对。
---
## 2. 名词解释
| 术语 | 说明 |
| :---------------- | :------------------------------------------------------------------------------------- |
| 助手模型 | 可在 AI 助手对话中选用的一条 DashScope 模型配置记录 |
| 模型编码 modelCode | 调用 DashScope `chat/completions` 时 JSON 中的 `model` 字段值(如 `qwen-turbo` |
| 展示名称 | 用户与管理后台列表中可见的中文/友好名称(如「通义 Turbo · 快速」) |
| 默认模型 | 系统指定的兜底模型;用户未手动选择或所选失效时使用 |
| 启用 / 禁用 | `enabled=true` 对用户可见可选、可新建会话;`false` 表示该模型 **Token 额度已用尽或运营下架**,用户不可再选,已绑定会话 **仅可查看** |
| 模型停用只读 | 会话绑定模型被禁用后:可加载历史消息全文,**禁止** `POST /chat`;交互同「满 5 轮只读」 |
| 逻辑删除 | `deleted=1`,列表默认不展示;历史会话仍可通过 `model_id` 关联到记录(含已删) |
| 会话绑定模型 | 每个 `ai_chat_session` 在创建时记录 `model_id`,该会话内所有轮次均用此模型 |
| 允许上传图片 | `allowImageUpload`,模型级配置;表示该模型 **能力上** 支持图片输入(本期用户端无上传 UI) |
| 允许上传语音 | `allowVoiceUpload`,模型级配置;表示该模型 **能力上** 支持语音输入(本期用户端无上传 UI) |
---
## 3. 功能需求清单
### 3.1 管理后台 - 助手模型管理
#### 3.1.1 功能描述
在管理后台(`AdminLayout`)新增菜单 **「AI 模型」**(或「助手模型」),路由建议 `/admin/ai-models`。页面提供模型列表与新增/编辑弹窗,支持逻辑删除。
#### 3.1.2 页面要素
| 列表列 | 说明 |
| :--------- | :---------------------------------------- |
| ID | 主键 |
| 展示名称 | `displayName` |
| 模型编码 | `modelCode`DashScope 实际 model 名) |
| 描述 | 可选,列表可省略或 tooltip |
| 状态 | 启用 / 禁用(禁用表示 Token 不足或运营下架) |
| 图片 | 允许上传图片(是/否) |
| 语音 | 允许上传语音(是/否) |
| 默认 | 是否默认模型(至多一个为「是」) |
| 排序 | `sort`,升序排列 |
| 更新时间 | `updatedAt` |
| 操作 | 编辑、删除(逻辑删除) |
弹窗表单字段见 §3.3.2。
#### 3.1.3 业务规则
| 编号 | 规则项 | 业务规则说明 |
| :---------------------- | :--------------- | :---------------------------------------------------------------------------------------------------- |
| FR-AI-MODEL-ADM-01 | 权限 | 仅 **ADMIN** 角色可访问;接口挂载 `/api/admin/ai/models/**` |
| FR-AI-MODEL-ADM-02 | 新增 | 必填:`displayName``modelCode`;可选:`description`;默认 `enabled=true``allowImageUpload=false``allowVoiceUpload=false``sort=0` |
| FR-AI-MODEL-ADM-03 | 修改 | 可改展示名、编码、描述、启用、排序、是否默认、`allowImageUpload``allowVoiceUpload`**不可** 修改 `id` |
| FR-AI-MODEL-ADM-03a | 多模态开关 | `allowImageUpload``allowVoiceUpload` 为布尔,默认 `false`;仅管理端表单配置,**本期无实际上传联调** |
| FR-AI-MODEL-ADM-04 | 逻辑删除 | 删除将 `deleted` 置 1;列表与下拉 **不再展示**;**不物理删行** |
| FR-AI-MODEL-ADM-05 | 编码唯一 | 未删除记录中 `model_code` **全局唯一**;重复则 `400` |
| FR-AI-MODEL-ADM-06 | 默认模型唯一 | 全库至多 **1**`is_default=1``deleted=0`;设置新默认时自动取消原默认 |
| FR-AI-MODEL-ADM-07 | 禁止删唯一启用 | 若删除或禁用后,**不存在** 任何 `enabled=1``deleted=0` 的模型,操作拒绝并提示 |
| FR-AI-MODEL-ADM-08 | 禁止删当前默认 | 逻辑删除 **默认模型** 前须先将另一条设为默认,否则拒绝 |
| FR-AI-MODEL-ADM-09 | 列表范围 | 管理端列表默认仅 `deleted=0`;可选筛选「含禁用」 |
| FR-AI-MODEL-ADM-10 | 模型编码格式 | `modelCode` 1~64 字符,允许字母数字、点、横线、下划线;trim 后校验 |
| FR-AI-MODEL-ADM-11 | 展示名称 | `displayName` 1~32 字符 |
| FR-AI-MODEL-ADM-12 | 描述 | `description` 最长 200 字符,可空 |
| FR-AI-MODEL-ADM-13 | 排序 | `sort` 为非负整数,列表按 `sort ASC, id ASC` |
| FR-AI-MODEL-ADM-14 | 首版无连通性测试 | 保存时不强制调用 DashScope 探测;错误模型在用户使用时报既有 AI 错误码(见 §3.4) |
#### 3.1.4 验收标准
| 编号 | 验证场景 | 预期结果 |
| :---------------------- | :------------------------------- | :------------------------------------------------- |
| AC-AI-MODEL-ADM-01 | 管理员新增两条模型 | 列表可见,用户端可选列表含启用项 |
| AC-AI-MODEL-ADM-02 | 重复 modelCode 新增 | 提示编码已存在 |
| AC-AI-MODEL-ADM-03 | 将 A 设为默认后再设 B 为默认 | 仅 B 为默认 |
| AC-AI-MODEL-ADM-04 | 删除默认模型(未改默认) | 操作失败并提示 |
| AC-AI-MODEL-ADM-05 | 禁用全部模型尝试 | 最后一次禁用失败并提示须保留至少一个启用模型 |
| AC-AI-MODEL-ADM-06 | 逻辑删除后管理端列表 | 不出现;历史会话详情仍可显示当时模型名(见 §3.4) |
| AC-AI-MODEL-ADM-07 | 编辑模型勾选「允许上传图片」 | 保存成功;列表/详情展示正确;用户端助手 **无** 上传入口 |
---
### 3.2 用户端 - AI 助手模型切换
#### 3.2.1 功能描述
在 AI 助手页(`AiAssistant.vue` / `AiChatPanel` 标题栏区域)增加 **模型选择器**`el-select` 或同级组件)。展示当前用户可选的启用模型列表;切换后影响 **后续新建会话** 的默认模型;**不改变** 已存在且已绑定模型的历史会话的模型。
#### 3.2.2 交互示意
```
┌──────────────────────────────────────────────────────────────┐
│ 会话标题 …… [ 模型:通义 Turbo ▼ ] 轮次 3/5 │
├──────────────────────────────────────────────────────────────┤
│ 消息区 …… │
└──────────────────────────────────────────────────────────────┘
```
- 下拉项:`displayName`;可选副文案 `description`(过长截断)
- 默认选中:用户上次选择(见 FR-AI-MODEL-FE-04)或系统默认模型
#### 3.2.3 业务规则
| 编号 | 规则项 | 业务规则说明 |
| :---------------------- | :----------------- | :---------------------------------------------------------------------------------------------- |
| FR-AI-MODEL-FE-01 | 可见性 | 未登录:不展示选择器(与发送需登录一致);已登录展示 |
| FR-AI-MODEL-FE-02 | 数据来源 | `GET /api/app/ai/models`,仅 `enabled=1``deleted=0`,按 `sort` 排序 |
| FR-AI-MODEL-FE-03 | 切换生效范围 | 切换仅更新「当前选用模型」;**点击「新会话」** 创建的会话绑定新模型;已在进行的会话 **不变** |
| FR-AI-MODEL-FE-04 | 记住选择 | 将 `modelId` 存入 `localStorage`key 建议 `jog_ai_assistant_model_id`);进入页时恢复 |
| FR-AI-MODEL-FE-05 | 失效回退 | 本地记录的 `modelId` 已禁用/删除时,回退为 **系统默认模型** |
| FR-AI-MODEL-FE-06 | 发送携带 | `POST /api/app/ai/chat` 请求体增加可选 `modelId`**创建会话** `POST /sessions` 可带 `modelId` |
| FR-AI-MODEL-FE-07 | 首条无 session | 无 `sessionId` 时先创建会话(带当前 `modelId`)再发送 |
| FR-AI-MODEL-FE-08 | 只读会话 | **已满 5 轮****绑定模型已禁用**:输入区禁用、仅可查看历史;选择器仍可切换(为下次新会话准备) |
| FR-AI-MODEL-FE-08a | 模型停用提示 | 绑定模型 `enabled=false` 时展示提示条:「该会话所用模型已停用(额度不足),仅可查看历史,请切换模型后新建会话」 |
| FR-AI-MODEL-FE-08b | 只读判定 | `readOnly = (roundCount >= 5) OR (session.modelEnabled === false)`;二者同时满足时优先展示模型停用提示(若适用) |
| FR-AI-MODEL-FE-09 | 加载失败 | 模型列表接口失败:展示选择器占位「模型加载失败」,发送时后端用默认模型(见 BE-05) |
| FR-AI-MODEL-FE-10 | 单模型 | 仅 1 个可选模型时仍展示选择器(或禁用下拉仅展示名称,二选一,**推荐仍展示** 以利扩展) |
#### 3.2.4 验收标准
| 编号 | 验证场景 | 预期结果 |
| :---------------------- | :----------------------------------------- | :----------------------------------------------------------- |
| AC-AI-MODEL-FE-01 | 切换模型后点「新会话」并提问 | 新会话使用新模型(响应 `model` 字段与之一致) |
| AC-AI-MODEL-FE-02 | 会话进行中切换模型后继续在同会话发送 | 仍使用会话创建时绑定的模型 |
| AC-AI-MODEL-FE-03 | 刷新页面 | 恢复上次选择的模型(若仍有效) |
| AC-AI-MODEL-FE-04 | 管理员禁用某模型后用户点选绑定该模型的旧会话 | 历史消息完整展示,输入区禁用,提示模型已停用;发送被拦截 |
| AC-AI-MODEL-FE-05 | 禁用模型且会话未满 5 轮 | 仍不可继续发送(只读),与满 5 轮表现一致(除提示文案) |
---
### 3.3 后端 - 模型数据与接口
#### 3.3.1 数据模型(Flyway
建议新增迁移 `V6__ai_assistant_model.sql`(版本号以仓库递增为准)。
**表 `ai_assistant_model`**
| 字段 | 类型 | 说明 |
| :------------ | :------------- | :--------------------------------------------- |
| id | BIGINT PK | 主键 |
| model_code | VARCHAR(64) | DashScope model 参数,未删除记录唯一 |
| display_name | VARCHAR(32) | 展示名称 |
| description | VARCHAR(200) | 描述,可空 |
| enabled | TINYINT | 1 启用 0 禁用(Token 不足或运营下架),默认 1 |
| allow_image | TINYINT | 1 允许 0 不允许,默认 0;模型是否支持图片输入 |
| allow_voice | TINYINT | 1 允许 0 不允许,默认 0;模型是否支持语音输入 |
| is_default | TINYINT | 1 默认 0 非默认,默认 0 |
| sort | INT | 排序,默认 0 |
| created_at | DATETIME | 创建时间 |
| updated_at | DATETIME | 更新时间 |
| deleted | TINYINT | 逻辑删除,默认 0 |
**索引**`UNIQUE uk_model_code (model_code, deleted)` 在 MySQL 下对软删唯一需用应用层校验或 `(model_code)` 唯一 + 删后改后缀;**首版采用应用层校验** `model_code``deleted=0` 间唯一。
**表 `ai_chat_session` 变更**
| 字段 | 类型 | 说明 |
| :-------- | :------ | :---------------------------------------- |
| model_id | BIGINT | 会话绑定模型,FK 逻辑关联 `ai_assistant_model.id`,可空 |
- 迁移时对已有会话:`model_id` 设为迁移时 **默认模型** 的 id
- 新会话:创建时写入当前解析得到的 `model_id`
**表 `ai_chat_message`**:首版 **不增加** `model_id`(以会话为准)。
#### 3.3.2 管理端接口 `/api/admin/ai/models`
| 方法 | 路径 | 说明 |
| :----- | :-------------------------------- | :--------------------------- |
| GET | `/api/admin/ai/models` | 列表(`deleted=0`,分页可选) |
| GET | `/api/admin/ai/models/{id}` | 详情 |
| POST | `/api/admin/ai/models` | 新增 |
| PUT | `/api/admin/ai/models/{id}` | 修改 |
| DELETE | `/api/admin/ai/models/{id}` | 逻辑删除 |
**新增/修改请求体示例**
```json
{
"modelCode": "qwen-plus",
"displayName": "通义 Plus",
"description": "更强推理,适合复杂问题",
"enabled": true,
"allowImageUpload": false,
"allowVoiceUpload": false,
"isDefault": false,
"sort": 10
}
```
**列表项响应字段**`id, modelCode, displayName, description, enabled, allowImageUpload, allowVoiceUpload, isDefault, sort, createdAt, updatedAt`
#### 3.3.3 用户端接口
| 方法 | 路径 | 说明 |
| :--- | :--------------------------- | :------------------------------------- |
| GET | `/api/app/ai/models` | 可选模型列表(仅启用、未删除) |
**列表项**`id, displayName, description, isDefault, allowImageUpload, allowVoiceUpload`**不返回** `modelCode`;多模态字段供后续前端使用,**本期助手页不展示上传控件**)
**扩展已有接口**
| 接口 | 变更 |
| :--------------------------- | :------------------------------------------------------------------- |
| `POST /api/app/ai/sessions` | 请求体可选 `{ "modelId": 1 }`;未传则用默认模型 |
| `POST /api/app/ai/chat` | 请求体可选 `modelId`;若传且与会话已绑定不一致,**忽略** 并以会话为准 |
| `GET /api/app/ai/sessions` | 列表项增加 `modelDisplayName``modelEnabled`(绑定模型是否仍启用) |
| `GET .../sessions/{id}/messages` | 可增加 `modelEnabled``readOnlyReason``ROUND_FULL` / `MODEL_DISABLED` / null |
#### 3.3.4 聊天业务规则(相对原 AI 助手需求的增量)
| 编号 | 规则项 | 业务规则说明 |
| :---------------------- | :--------------- | :---------------------------------------------------------------------------------------------------- |
| FR-AI-MODEL-BE-01 | 解析模型 | 按 `model_id` 查模型;`deleted=1` 视为不可用;`enabled=0`**禁止** 对该会话执行 `POST /chat`(返回 `400`,文案:该会话所用模型已停用,仅可查看历史) |
| FR-AI-MODEL-BE-01a | 只读与满 5 轮 | `enabled=0``round_count>=5` 均导致只读;二者独立判断,消息列表接口返回 `readOnlyReason` 供前端展示 |
| FR-AI-MODEL-BE-02 | 新会话绑定 | `createSession` 解析 `modelId`:无效则用默认模型 id |
| FR-AI-MODEL-BE-03 | 调用 DashScope | 请求体 `model` 使用记录的 `model_code`**不再** 使用 `DashScopeProperties.model`(仅 AI 助手) |
| FR-AI-MODEL-BE-04 | 其它 AI 功能 | 摘要、润色、标签推荐 **仍** 使用 `properties.getModel()`,不读模型表 |
| FR-AI-MODEL-BE-05 | 默认模型 | 无可用默认时(数据异常),回退 `dashscope.model` 并打 **warn** 日志;正常运营不应出现 |
| FR-AI-MODEL-BE-06 | 模型不存在 | 创建会话时 `modelId` 指向已删或不存在 → `400` 模型不可用 |
| FR-AI-MODEL-BE-07 | 响应 | `AiChatResultVO` 继续返回实际 `model`modelCode)及可增加 `modelDisplayName` |
| FR-AI-MODEL-BE-08 | 限流与 Key | 与现有一致;不按模型分限流 |
| FR-AI-MODEL-BE-09 | 种子数据 | 迁移插入一条默认模型:`model_code` = 当前 `dashscope.model``display_name` = 「默认模型」,`is_default=1``allow_image=0``allow_voice=0` |
| FR-AI-MODEL-BE-10 | 多模态配置 | 保存 `allow_image`/`allow_voice``POST /chat` **本期不接收** 图片/语音附件;后续迭代再校验「模型不允许则拒绝上传」 |
#### 3.3.5 错误码补充
| 错误码 / HTTP | 描述 | 触发条件 |
| :------------ | :----------------- | :------------------------------------- |
| 400 | 模型编码重复 | 新增/修改 modelCode 冲突 |
| 400 | 模型不可用 | 用户指定 modelId 无效或已删除 |
| 400 | 须保留启用模型 | 禁用/删除导致无启用模型 |
| 400 | 须先更换默认模型 | 删除当前默认模型 |
| 400 | 模型已停用 | 对绑定已禁用模型的会话调用 `POST /chat` |
(可在 `ErrorCode` 中新增枚举,或使用 `BusinessException` + 明确文案。)
#### 3.3.6 验收标准
| 编号 | 验证场景 | 预期结果 |
| :---------------------- | :------------------------------------ | :-------------------------------------------- |
| AC-AI-MODEL-BE-01 | 两模型分别建会话并 chat | DashScope 请求 `model` 分别为对应 modelCode |
| AC-AI-MODEL-BE-02 | 禁用某模型后旧会话继续 chat | 返回 400,模型已停用;`GET messages` 仍可查看 |
| AC-AI-MODEL-BE-03 | 新会话不传 modelId | 使用默认模型 |
| AC-AI-MODEL-BE-04 | 非管理员访问 admin 接口 | 403 |
---
### 3.4 配置与部署
| 配置项 | 说明 |
| :------------------ | :------------------------------------------------------------------- |
| `DASHSCOPE_API_KEY` | 不变,全局一份 |
| `dashscope.model` | 作 **种子****兜底**;AI 助手正常运行后以库内默认模型为准 |
| Flyway | 执行 V6 迁移 + 种子数据 |
部署检查:管理端可 CRUD;用户端下拉有数据;新会话对话返回正确 `model` 字段。
---
## 4. 交互流程
### 4.1 管理员上架新模型
```
1. 管理员进入 /admin/ai-models
2. 点击新增,填写 modelCode、displayName、enabled、sort
3. POST /api/admin/ai/models → 列表刷新
4. 用户端 GET /api/app/ai/models 出现新项(若 enabled
```
### 4.2 用户切换模型并新会话提问
```
1. 用户在 AI 助手页将模型从 A 改为 B(写入 localStorage
2. 点击「新会话」→ POST /sessions { modelId: B } → session 绑定 B
3. POST /chat → DashScope model = B.modelCode
4. 响应 data.model 为 B 的 modelCode
```
### 4.3 历史会话回看(模型已禁用)
```
1. 管理员因 Token 不足将模型 A 设为 disabled
2. 用户点选绑定模型 A 的旧会话(可能未满 5 轮)
3. GET /sessions/{id}/messages → 返回全文 + modelEnabled=false + readOnlyReason=MODEL_DISABLED
4. 前端输入区禁用,展示「模型已停用,仅可查看」
5. 用户 POST /chat → 400 拒绝
6. 用户切换为启用中的模型 B,点击「新会话」后可正常对话
```
---
## 5. 接口与代码对齐(规划)
### 5.1 后端
| 模块 | 文件 | 变更说明 |
| :-------------- | :------------------------------------------------ | :-------------------------- |
| jog-admin | `db/migration/V6__ai_assistant_model.sql` | 模型表 + session.model_id |
| jog-module-blog | `entity/AiAssistantModel.java` | 实体 |
| jog-module-blog | `service/AiAssistantModelService` + impl | CRUD、默认模型、校验 |
| jog-module-blog | `controller/AdminAiModelController.java` | `/api/admin/ai/models` |
| jog-module-blog | `controller/AiChatController` 或新 App 方法 | `GET /api/app/ai/models` |
| jog-module-blog | `AiChatServiceImpl` | 按 session.model_id 调模型 |
### 5.2 前端
| 模块 | 文件 | 变更说明 |
| :----------- | :--------------------------- | :-------------------- |
| jog-frontend | `views/admin/AiModels.vue` | 模型管理页 |
| jog-frontend | `layouts/AdminLayout.vue` | 菜单项 |
| jog-frontend | `router/index.js` | `/admin/ai-models` |
| jog-frontend | `api/aiModel.js` | admin + app API |
| jog-frontend | `AiAssistant.vue` / Panel | 模型选择器 |
| jog-frontend | `api/aiChat.js` | sessions/chat 带 modelId |
---
## 6. 非功能性需求
| 编号 | 类型 | 需求说明 |
| :----- | :------- | :----------------------------------------------------------------------- |
| NFR-01 | 性能 | 模型列表接口轻量,无分页或 pageSize≤50;不增加 chat 接口 P95 目标 |
| NFR-02 | 安全 | `modelCode` 仅管理端可见;防 SQL 注入沿用 MyBatis-PlusAdmin 鉴权 |
| NFR-03 | 兼容 | 迁移后旧会话均有 `model_id`;不影响已存消息 |
| NFR-04 | 可观测性 | chat 日志记录 `modelId``modelCode`(不记 API Key |
| NFR-05 | 可扩展性 | 表结构预留 `allow_image`/`allow_voice` 及后续 per-model `temperature` 等 |
---
## 7. 范围外(后续迭代)
| 项 | 说明 |
| :----------------- | :----------------------------------------- |
| 其它 AI 功能共用模型表 | 摘要/润色/标签推荐改为可选模型 |
| 模型连通性检测 | 保存时试调用 DashScope |
| 按模型差异化限流 | 不同模型不同每分钟次数 |
| 多 API Key | 不同模型不同 Key(多租户) |
| 用户级模型权限 | VIP 可用 plus 等 |
| 流式输出按模型配置 | 与 SSE 需求一并考虑 |
| 用户端图片上传 | 助手页上传图片 UI 及 `POST /chat` 附件协议 |
| 用户端语音上传 | 助手页语音录入/上传及转写联调 |
---
## 8. 风险与缓解
| 风险 | 影响 | 缓解措施 |
| :----------------------- | :------------- | :-------------------------------------------- |
| 错误 modelCode | 调用失败 | 管理端文案说明须与百炼文档一致;用户侧友好错误 |
| 删除模型后会话孤儿 | 展示异常 | 逻辑删保留行;展示时用快照名或「已下架模型」 |
| 中途换模导致上下文混乱 | 回答质量下降 | 会话级绑定,仅新会话换模(D-08) |
| 无启用模型 | 全站助手不可用 | ADM-07 + 种子默认模型 |
| 禁用后会话仍被误发 | 继续扣 Token | 前后端双重只读;`POST /chat` 校验 `enabled` |
---
## 9. 产品约定(建议默认,评审可调整)
| 编号 | 约定项 | 需求说明 |
| :--- | :----- | :------- |
| D-08 | 会话级绑定 | 模型在 **创建会话** 时绑定;同会话多轮不换模;用户切换选择器只影响 **之后的新会话** |
| D-09 | 禁用仅可查看 | 模型禁用主因:**Token 额度不足**(或运营下架);绑定该模型的会话 **仅可查看历史**,不可 `POST /chat`;新会话不可再选该模型 |
| D-10 | 仅助手用模型表 | 摘要、润色、标签推荐继续 `dashscope.model` |
| D-11 | 首版种子一条 | 迁移从当前配置插入默认模型,保证零配置升级可用 |
| D-12 | 用户不看 modelCode | App 列表只展示 `displayName`;实际编码仅管理端与日志 |
| D-13 | 多模态先配置后能力 | 管理端可配置 `allowImageUpload` / `allowVoiceUpload`;**本期用户端不提供** 图片/语音上传 UI,字段入库并随 API 返回供后续使用 |
---
## 10. 需求完整性自检
| 检查项 | 状态 | 说明 |
| :----- | :--- | :--- |
| 管理员增删改查 | ✅ | §3.1 + §3.3.2 |
| 删除为逻辑删除 | ✅ | FR-AI-MODEL-ADM-04 |
| 用户端模型切换 | ✅ | §3.2 |
| 与多轮/5 轮/持久化兼容 | ✅ | 不改变原 FR-AI-CHAT-*,仅扩展 session |
| 默认与边界约束 | ✅ | 唯一默认、至少一启用、删默认前换默认 |
| 数据迁移与旧数据 | ✅ | session.model_id 回填 |
| 安全与权限 | ✅ | Admin vs App 接口分离 |
| 前端记住选择 | ✅ | FR-AI-MODEL-FE-04(小需求,提升体验) |
| 历史回看展示模型 | ✅ | 会话列表带 modelDisplayName、`modelEnabled` |
| 禁用后会话只读 | ✅ | D-09、FR-AI-MODEL-BE-01、FE-08a |
| 图片/语音开关配置 | ✅ | 管理端表单 + 表字段;用户端上传见 §7 |
**已确认约定**
1. **禁用模型旧会话** — D-09:仅可查看,不可继续对话(Token 不足场景)。
2. **其它 AI 功能是否纳入模型表** — D-10:首版不纳入。
3. **图片/语音** — D-13:后台可配,用户端上传 UI 本期不做。
---
**编写人:** AI 助手
**评审人:** 已确认(D-09 禁用仅查看、D-13 多模态后台配置)
**生效日期:** 2026-05-23
@@ -0,0 +1,210 @@
# AI 助手联调验收 vs 模型管理 — 冲突分析
| 文档版本 | 修订日期 | 说明 |
| :------- | :--------- | :--- |
| V1.0 | 2026-05-23 | 助手功能联调/验收进行中时,与《AI助手模型管理功能需求》V1.2 的差异与冲突梳理 |
**关联文档**
- [AI助手功能需求.md](./AI助手功能需求.md)V1.2,联调验收基准)
- [AI助手功能技术实施文档.md](./AI助手功能技术实施文档.md)(待办 23~25:联调 🔄、验收 ⬜)
- [AI助手模型管理功能需求.md](./AI助手模型管理功能需求.md)V1.2
- [AI助手模型管理功能技术实施文档.md](./AI助手模型管理功能技术实施文档.md)V1.1,未实施)
**分析基准**:当前仓库已实现助手 V5 表 + 四接口 + `AiAssistant.vue`,模型管理 **尚未编码**
---
## 1. 结论摘要
| 类别 | 数量 | 对联调验收的影响 |
| :--- | :--- | :--- |
| **P0 破坏性冲突**(不协同发布会导致助手页异常) | **1** | 必须先完成助手 AC 或做兼容层,再上线模型管理后端 |
| **P1 行为/语义冲突**(验收口径变化,需补充用例) | **4** | 不阻断当前联调,但模型管理上线后原 AC 覆盖不足 |
| **P2 环境与流程冲突**(库表、分支、验收顺序) | **3** | 联调环境执行 V6 后需重跑部分 AC |
| **P3 扩展性差异**UI/接口增量,无逻辑矛盾) | **5** | 与当前联调并行开发需注意合并顺序 |
| **无冲突(可并存)** | **12+** | 多轮、限流、登录、重试、分页等规则一致 |
**合计需纳入排期的冲突/差异项:8 项(P0×1 + P1×4 + P2×3**;P3 五项建议在模型管理 PR 中一并交付,但不计入「硬冲突」。
**推荐节奏**
1. **先** 按《AI助手功能需求》完成待办 23~25(AC-AI-CHAT-*),基线环境 **仅 V5、不部署 V6**
2. **再** 实施模型管理;`GET .../messages` 与前端 **同一 PR** 发布,或后端短期兼容旧响应格式。
3. 模型管理验收使用 **AC-AI-MODEL-***,并 **追加** 4 条 P1 回归(见 §5)。
---
## 2. 冲突项清单
### 2.1 P0 — 破坏性冲突(1 项)
| 编号 | 冲突点 | 助手现状(联调基准) | 模型管理要求 | 影响 |
| :--- | :----- | :------------------- | :----------- | :--- |
| **C-P0-01** | `GET /api/app/ai/sessions/{id}/messages` 响应结构 | `ApiResult.data`**消息数组** `List<AiChatMessageVO>`(需求 FR-AI-CHAT-BE-07);前端 `messages.value = res.data` | `data` 改为 **包装对象**`{ messages, modelEnabled, readOnlyReason, roundCount, maxRounds }` | 仅升级后端时,前端把对象当数组渲染 → **消息区空白或报错** |
**缓解**(择一):
- **A(推荐)**:模型管理前后端同版本发布;联调环境在助手 AC 通过后再合入。
- **B**:后端过渡期双格式:`?legacy=true` 仍返回数组,默认新格式。
- **C**:前端先判 `Array.isArray(data)`,兼容两种结构。
---
### 2.2 P1 — 行为 / 语义冲突(4 项)
| 编号 | 冲突点 | 助手现状 | 模型管理要求 | 对联调/验收的影响 |
| :--- | :----- | :------- | :----------- | :---------------- |
| **C-P1-01** | 调用 DashScope 的 `model` 来源 | FR-AI-CHAT-BE-09`DashScopeProperties.model``DASHSCOPE_MODEL` | FR-AI-MODEL-BE-03:会话绑定 `ai_assistant_model.model_code` | 联调时记录的 `response.model` 来自 yml;上线后来自 **库表**。同一环境改 `DASHSCOPE_MODEL` **不再** 改变已有助手会话行为 |
| **C-P1-02** | 只读条件 | FR-AI-CHAT-FE-05:仅 `roundCount >= 5` → 只读 | 另增 `modelEnabled === false`(D-09);未满 5 轮也可只读 | AC-AI-CHAT-FE-04/05 **未覆盖**「模型禁用」;模型管理验收需 AC-AI-MODEL-FE-04/05 |
| **C-P1-03** | `POST /chat` 拒绝条件 | 满 5 轮、参数非法、限流、AI 异常 | 绑定模型 `enabled=0`**400 模型已停用**FR-AI-MODEL-BE-01) | 联调阶段不会出现;管理员禁用模型后,原「未满 5 轮可继续」会话 **不能再发** — 与 FR-AI-CHAT-FE-05 文案/预期 **不一致**(属需求演进,非实现 bug |
| **C-P1-04** | 创建会话与模型绑定 | `POST /sessions` **无 body**`ai_chat_session` **无** `model_id` | 可选 `{ modelId }`;表字段 `model_id` 必填(迁移回填) | 联调创建的会话在 V6 后 `model_id` 指向种子默认模型;**不会** 自动等同于当时 yml 中的 model(若种子与 yml 不一致) |
---
### 2.3 P2 — 环境 / 流程冲突(3 项)
| 编号 | 冲突点 | 说明 | 建议 |
| :--- | :----- | :--- | :--- |
| **C-P2-01** | Flyway V6 与联调库 | 联调库若已跑 V5 并产生会话,执行 V6 会 `ALTER` + 种子 + `UPDATE model_id` | 助手 AC 在 **V5-only** 环境签字;V6 后重跑:刷新恢复、历史点选、发送(C-P0-01 相关) |
| **C-P2-02** | 验收清单并行 | 《AI助手功能技术实施文档》待办 25:`AC-AI-CHAT-*`;《模型管理》`AC-AI-MODEL-*` | 分两轮签字,避免混在同一表格未标注版本 |
| **C-P2-03** | 默认模型双配置 | 助手联调看 `application.yml` / `DASHSCOPE_MODEL`;模型管理运行看库内 `is_default` | 部署检查清单增加:**种子 `model_code` 与 yml 一致**(见模型管理需求 §3.4) |
---
### 2.4 P3 — 扩展差异(非硬冲突,5 项)
| 编号 | 差异点 | 助手现状 | 模型管理增量 | 说明 |
| :--- | :----- | :------- | :----------- | :--- |
| **C-P3-01** | 新接口 | 4 个 app 接口 | `GET /api/app/ai/models` | 纯增量,不影响联调 |
| **C-P3-02** | 会话列表字段 | `id, title, roundCount, lastMessageAt` | + `modelDisplayName`, `modelEnabled` | 向后兼容,旧前端可忽略 |
| **C-P3-03** | 创建会话 body | 空 POST | 可选 `modelId` | 旧客户端不传即可 |
| **C-P3-04** | 页面布局 | 标题栏仅「会话标题 + 轮次」 | 增加模型 `el-select`FR-AI-MODEL-FE | 布局扩展,不推翻 FR-AI-CHAT-FE 分栏 |
| **C-P3-05** | localStorage | FR-AI-CHAT-FE-14:不存对话正文 | 仅存 `jog_ai_assistant_model_id` | 不冲突 |
---
### 2.5 无冲突项(联调可继续,12 项代表)
以下规则在两份需求中 **一致或模型管理显式不改**
| 领域 | 助手需求编号 | 说明 |
| :--- | :----------- | :--- |
| 多轮上限 5 轮 | FR-AI-CHAT-BE-02、FE-05/06 | 模型管理未改 `MAX_ROUNDS` |
| 失败重试不重复 user | FR-AI-CHAT-BE-15 | 不变 |
| 限流 10 次/分钟 | FR-AI-CHAT-BE-12 | FR-AI-MODEL-BE-08 明确不按模型分限流 |
| 登录与鉴权 | FR-AI-CHAT-FE-10、BE-01 | 不变 |
| 会话列表分页 50 | FR-AI-CHAT-FE-16、D-07 | 不变 |
| 历史全文回看 | FR-AI-CHAT-FE-04、D-01 | 不变(仅多只读原因) |
| 新会话不删旧数据 | FR-AI-CHAT-FE-02、D-03 | 不变 |
| 进入页默认最近会话 | D-06 | 不变 |
| System Prompt / max-input 截断 | FR-AI-CHAT-BE-03/04/10 | 不变 |
| 会话软删除预留 | FR-AI-CHAT-BE-14 | 模型管理不强制前端删会话 |
| 润色/标签/摘要模型来源 | D-10、§1.6 | 与助手联调无关 |
| `POST /chat` 请求体核心字段 | `sessionId` + `message` | 仍必填;`modelId` 可选且可忽略 |
---
## 3. 接口契约对照表
| 接口 | 联调中契约 | 模型管理后契约 | 冲突级别 |
| :--- | :--------- | :------------- | :------- |
| `POST /sessions` | 无 body → `{ sessionId }` | 可选 body `{ modelId }` → 同上 | 兼容 |
| `GET /sessions` | 分页 + 4 字段 | +2 字段 | 兼容 |
| `GET /sessions/{id}/messages` | `data`: **数组** | `data`: **对象** | **C-P0-01** |
| `POST /chat` | `{ sessionId, message }` | +可选 `modelId`(忽略) | 兼容 |
| `GET /models` | 不存在 | 新增 | 无 |
---
## 4. 前端实现差异(当前代码)
| 文件 | 联调实现 | 模型管理需改 |
| :--- | :------- | :----------- |
| `api/aiChat.js` | `createAiChatSession()` 无参;messages 按数组解析 | 传 `modelId`;解析 `payload.messages` |
| `AiAssistant.vue` | `readOnly = roundCount >= 5``res.data` 作消息列表 | 合并 `modelEnabled`;处理 `readOnlyReason` |
| `AiChatPanel.vue` | 仅满 5 轮 `readonly-banner` | 增加模型停用提示条 |
| (无) | 无模型选择器 | 标题栏 `el-select` + `listAppAiModels` |
---
## 5. 验收用例冲突与合并建议
### 5.1 当前联调应完成的范围(不受模型管理阻塞)
以下用例 **仅依赖 V5 + 现有代码**,与模型管理无硬冲突:
- AC-AI-CHAT-NAV-*、AC-AI-CHAT-FE-0103、0607
- AC-AI-CHAT-FE-04、05**仅满 5 轮只读**
- AC-AI-CHAT-BE-0105
对应技术文档待办:**23、24、25(助手部分)**。
### 5.2 模型管理上线后必须追加的回归(原 AC 未覆盖)
| 追加编号 | 场景 | 对应冲突 |
| :------- | :--- | :------- |
| REG-MODEL-01 | V6 迁移后,旧会话可加载消息且可对话(默认模型启用) | C-P1-04、C-P2-01 |
| REG-MODEL-02 | 禁用绑定模型后,未满 5 轮会话只读且 chat 400 | C-P1-02、C-P1-03 |
| REG-MODEL-03 | 切换模型 + 新会话,DashScope 使用新 `model_code` | C-P1-01 |
| REG-MODEL-04 | 前端 messages 包装结构渲染正常 | C-P0-01 |
### 5.3 不必在助手联调阶段执行的用例
- AC-AI-MODEL-ADM-*、AC-AI-MODEL-FE-0105、AC-AI-MODEL-BE-*(属模型管理专项验收)
---
## 6. 测试与自动化影响
| 资产 | 联调基准 | 模型管理后 |
| :--- | :------- | :--------- |
| `AiChatServiceImplTest` | Mock `properties.getModel()` | 需 Mock `AiAssistantModelMapper` / Service |
| `AiChatServiceIntegrationTest` | 内存实现,无 model_id | 补 model 绑定与禁用用例 |
| `AiChatServiceLiveIT` | 打印 `model` 来自配置 | 可能为库表 `model_code` |
| 前端 E2E(若有) | 无模型下拉 | 需新场景 |
**冲突项计数**:自动化需 **显著改动** 的约 **2 处**(Service 单测 + 集成测),计入 P1 维护成本,不单列新编号。
---
## 7. 分支与发布策略建议
```
main ──┬── [阶段1] 助手联调验收(V5 only,签 AC-AI-CHAT-*
└── [阶段2] feature/ai-model-mgmtV6 + 模型 CRUD + 助手改造)
└── 同一 PR 合并:后端 messages 包装 + 前端兼容/新 UI
└── 签 AC-AI-MODEL-* + REG-MODEL-0104
```
- **避免**:阶段 1 未完成时合入仅含 V6 + 后端 messages 改动的 PR。
- **允许**:阶段 1 期间在独立分支开发模型管理,但不部署到联调验收环境。
---
## 8. 冲突项统计(速查)
| 级别 | 编号 | 计数 |
| :--- | :--- | :--- |
| P0 | C-P0-01 | 1 |
| P1 | C-P1-01 C-P1-04 | 4 |
| P2 | C-P2-01 C-P2-03 | 3 |
| P3 | C-P3-01 C-P3-05 | 5(扩展,非阻断) |
| **需协调的硬冲突合计** | P0 + P1 + P2 | **8** |
---
## 9. 文档修订建议(可选)
| 文档 | 建议 |
| :--- | :--- |
| 《AI助手功能需求》 | 在 §3.3.3 增加脚注:「消息列表响应以模型管理上线版为准,见《AI助手联调与模型管理冲突分析》C-P0-01」 |
| 《AI助手模型管理功能需求》 | §3.3.3 标明对 FR-AI-CHAT-BE-07 的 **替代关系** |
| 《AI助手功能技术实施文档》 | 待办 25 备注:验收环境 Flyway ≤ V5 |
---
**编写人:** AI 助手
**评审人:** 待评审
**生效日期:** 2026-05-23
@@ -253,6 +253,14 @@
---
## 8. 与其它 AI 功能的关系
本功能所用模型由配置项 **`dashscope.model`**(环境变量 `DASHSCOPE_MODEL`,默认 `qwen-turbo`)指定,经 `DashScopeProperties` 注入润色接口,**不使用**《AI助手模型管理功能需求》中的 `ai_assistant_model` 表。助手侧模型的新增、禁用、用户选模 **不影响** 润色行为。
**文章标签自动推荐**、**文章摘要** 同属编辑器 / 写作类一次性调用,共用 API Key 与底层 `callModel` 能力,限流 key 与 AI 助手对话 **相互独立**。若需全站统一按模型管理额度,属后续迭代(见助手模型需求 §7「其它 AI 功能共用模型表」),**不在** 润色本期范围。
---
**编写人:** AI 助手
**评审人:** 待评审
**生效日期:** 2026-05-23
@@ -1,5 +1,7 @@
# AI 文章润色功能技术实施文档
**与其它 AI 功能**:润色始终使用 `DashScopeProperties.model`,与《AI助手模型管理功能需求》中的 `ai_assistant_model` **无关**;实施或联调助手模型管理时 **无需改动** 本模块。参见润色需求文档 §8。
## 前提条件
**数据安全规则**
@@ -1,5 +1,7 @@
# 文章标签自动推荐功能技术实施文档
**与其它 AI 功能**:标签推荐使用 `properties.getModel()`,**不接入** 助手模型表;实施助手模型管理时 **无需改动** `TagRecommendServiceImpl`。参见《文章标签自动推荐功能需求》§6 及《AI助手模型管理功能需求》D-10。
## 前提条件
**数据安全规则**
+3 -1
View File
@@ -76,9 +76,11 @@
## 6. 技术实现
- 后端:复用现有的 AI 服务调用能力
- 模型:使用项目现有的 AI 模型配置
- 模型:使用配置项 **`dashscope.model`**(环境变量 `DASHSCOPE_MODEL`),**不使用** AI 助手模型管理表 `ai_assistant_model`;助手后台对模型的启用 / 禁用 **不影响** 本功能(与《AI助手模型管理功能需求》约定 D-10 一致)
- 前端:在文章编辑页面添加推荐按钮和交互
与 **AI 文章润色**、**文章摘要** 共用 DashScope API Key 与 `callModel` 调用链;与 **AI 助手** 对话的模型选择、会话绑定 **无耦合**。全站统一后台配模见助手模型需求范围外说明,非本期内容。
## 7. 非功能需求
- 推荐响应时间应在 5 秒内完成
+362 -2078
View File
File diff suppressed because it is too large Load Diff