feat(review-ai): 平板 AI 助手面板 MVP

新增功能:
- src/api/review/ai.ts:5 个 AI API(摘要、重建、会话、流式问答、清空)
- 平板页右侧 AI 助手面板(toggleable 抽屉,三栏布局)
  - 摘要状态卡片:未生成/生成中(轮询4s)/已完成/失败
  - 结构化摘要展示:概述/目标/范围/计划/预算/来源文件
  - 共享问答区:历史消息加载 + 流式生成气泡
  - 4个快捷问题按钮
  - 停止生成 / 清空当前项目问答 按钮
  - 回车快捷发送

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
pull/874/head
Codewoc 2026-03-23 22:50:41 +08:00
parent 302772ecb9
commit e6cf930062
2 changed files with 701 additions and 214 deletions

99
src/api/review/ai.ts Normal file
View File

@ -0,0 +1,99 @@
import request from '@/config/axios'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { getAccessToken } from '@/utils/auth'
import { config } from '@/config/axios/config'
// ============================================================
// 类型定义
// ============================================================
export const AI_SUMMARY_STATUS = {
NOT_BUILT: 0,
BUILDING: 1,
SUCCESS: 2,
FAILED: 3
} as const
export interface ReviewAiSummaryVO {
reviewMeetingProjectId: number
status: number
statusName: string
updatedTime: string | null
summaryMarkdown: string | null
sourceFileIds: number[]
errorMessage: string | null
summary: {
projectOverview: string
businessGoal: string
constructionScope: string
implementationPlan: string
budgetInfo: string
keyEntities: string[]
readingGuide: string[]
sourceFiles: string[]
} | null
}
export interface ReviewAiConversationVO {
conversationId: number
title: string
knowledgeId: number
}
// ============================================================
// API 调用
// ============================================================
/** 获取评审项目 AI 摘要 */
export const getProjectAiSummary = (reviewMeetingProjectId: number): Promise<ReviewAiSummaryVO> =>
request.get({ url: `/project/review-ai/project/${reviewMeetingProjectId}/summary` })
/** 触发重建 AI 摘要(管理端) */
export const rebuildProjectAiSummary = (reviewMeetingProjectId: number) =>
request.post({ url: `/project/review-ai/project/${reviewMeetingProjectId}/rebuild` })
/** 打开 / 获取当前项目共享会话 */
export const openProjectAiConversation = (
reviewMeetingProjectId: number
): Promise<ReviewAiConversationVO> =>
request.post({ url: `/project/review-ai/project/${reviewMeetingProjectId}/conversation/open` })
/** 清空当前项目共享会话消息 */
export const clearProjectAiMessages = (reviewMeetingProjectId: number) =>
request.delete({ url: `/project/review-ai/project/${reviewMeetingProjectId}/chat/messages` })
/**
* AI
* 使 fetchEventSourceaxios SSE
*/
export const sendProjectAiChatStream = (
reviewMeetingProjectId: number,
conversationId: number,
content: string,
ctrl: AbortController,
onMessage: (event: any) => void,
onError: (error: any) => void,
onClose: () => void
) => {
const token = getAccessToken()
return fetchEventSource(
`${config.base_url}/project/review-ai/project/${reviewMeetingProjectId}/chat/send-stream`,
{
method: 'post',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`
},
openWhenHidden: true,
body: JSON.stringify({
conversationId,
content,
useContext: true
}),
onmessage: onMessage,
onerror: onError,
onclose: onClose,
signal: ctrl.signal
}
)
}

File diff suppressed because it is too large Load Diff