feat: 审核体系全面改造 — 多维度评分 + 卖点优先级 + AI 语义匹配 + 品牌方 AI 状态通知

后端:
- 审核结果拆分为 4 个独立维度 (法规合规/平台规则/品牌安全/Brief匹配度)
- 卖点优先级从 required:bool 改为三级 (core/recommended/reference)
- AI 语义匹配卖点覆盖 + AI 整体 Brief 匹配度分析
- BriefMatchDetail 评分详情 (覆盖率+亮点+问题点)
- min_selling_points 代理商可配置最少卖点数 + Alembic 迁移
- AI 语境复核过滤误报
- Brief AI 解析 + 规则 AI 解析
- AI 未配置/异常时通知品牌方
- 种子数据更新 (新格式审核结果+brief_match_detail)

前端:
- 三端审核页面展示四维度评分卡片
- 卖点编辑改为三级优先级选择器
- BriefMatchDetail 展示 (覆盖率进度条+亮点+问题)
- min_selling_points 配置 UI
- AI 配置页未配置时静默处理
- 文件预览/下载/签名 URL 优化

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-02-11 19:11:54 +08:00
co-authored by Claude Opus 4.6
parent 0c59797d5b
commit 0ef7650c09
43 changed files with 3909 additions and 1316 deletions
+72 -4
View File
@@ -473,14 +473,44 @@ class ApiClient {
/**
* 获取私有桶文件的预签名访问 URL
*/
async getSignedUrl(url: string, expire: number = 3600, download: boolean = false): Promise<string> {
async getSignedUrl(url: string): Promise<string> {
const response = await this.client.get<{ signed_url: string; expire_seconds: number }>(
'/upload/sign-url',
{ params: { url, expire, download } }
{ params: { url } }
)
return response.data.signed_url
}
/**
* 通过后端代理获取文件预览 Blob URL(用于 iframe/img src
*/
async getPreviewUrl(fileUrl: string): Promise<string> {
const response = await this.client.get('/upload/preview', {
params: { url: fileUrl },
responseType: 'blob',
})
return URL.createObjectURL(response.data)
}
/**
* 通过后端代理下载文件(避免 TOS CORS 问题)
*/
async downloadFile(fileUrl: string, filename: string): Promise<void> {
const response = await this.client.get('/upload/download', {
params: { url: fileUrl },
responseType: 'blob',
})
const blob = new Blob([response.data])
const blobUrl = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = blobUrl
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
URL.revokeObjectURL(blobUrl)
}
// ==================== 视频审核 ====================
/**
@@ -524,9 +554,9 @@ class ApiClient {
/**
* 查询任务列表
*/
async listTasks(page: number = 1, pageSize: number = 20, stage?: TaskStage): Promise<TaskListResponse> {
async listTasks(page: number = 1, pageSize: number = 20, stage?: TaskStage, projectId?: string): Promise<TaskListResponse> {
const response = await this.client.get<TaskListResponse>('/tasks', {
params: { page, page_size: pageSize, stage },
params: { page, page_size: pageSize, stage, project_id: projectId },
})
return response.data
}
@@ -677,6 +707,37 @@ class ApiClient {
return response.data
}
/**
* 代理商更新 Briefagency_attachments + selling_points + blacklist_words
*/
async updateBriefByAgency(projectId: string, data: {
agency_attachments?: Array<{ id?: string; name: string; url: string; size?: string }>
selling_points?: Array<{ content: string; required: boolean }>
blacklist_words?: Array<{ word: string; reason: string }>
brand_tone?: string
other_requirements?: string
min_selling_points?: number | null
}): Promise<BriefResponse> {
const response = await this.client.patch<BriefResponse>(`/projects/${projectId}/brief/agency-attachments`, data)
return response.data
}
/**
* AI 解析 Brief 文档
*/
async parseBrief(projectId: string): Promise<{
product_name: string
target_audience: string
content_requirements: string
selling_points: Array<{ content: string; required: boolean }>
blacklist_words: Array<{ word: string; reason: string }>
}> {
const response = await this.client.post(`/projects/${projectId}/brief/parse`, null, {
timeout: 180000, // 3 分钟,文档下载 + AI 解析较慢
})
return response.data
}
// ==================== 组织关系 ====================
/**
@@ -841,6 +902,13 @@ class ApiClient {
return response.data
}
/**
* 删除白名单
*/
async deleteWhitelistItem(id: string): Promise<void> {
await this.client.delete(`/rules/whitelist/${id}`)
}
/**
* 查询竞品列表
*/