fix(review-tablet): 修复AI助手项目切换状态

pull/874/head
Codewoc 2026-03-27 14:25:13 +08:00
parent 91b550fb1e
commit ee816f95c7
1 changed files with 70 additions and 7 deletions

View File

@ -144,7 +144,7 @@
<!-- 未生成 -->
<div v-if="!aiSummary || aiSummary.status === 0" class="ai-summary-empty">
<el-icon><InfoFilled /></el-icon>
<span>当前项目尚未生成 AI 摘要请上传资料后自动触发生成</span>
<span>{{ aiSummary?.blockReason || '当前项目 AI 尚未就绪,请联系管理员先完成构建' }}</span>
</div>
<!-- 生成中 -->
<div v-else-if="aiSummary.status === 1" class="ai-summary-building">
@ -154,7 +154,7 @@
<!-- 失败 -->
<div v-else-if="aiSummary.status === 3" class="ai-summary-failed">
<el-icon><WarningFilled /></el-icon>
<span>摘要生成失败{{ aiSummary.errorMessage || '未知错误' }}</span>
<span>{{ aiSummary.blockReason || `摘要生成失败:${aiSummary.errorMessage || '未知错误'}` }}</span>
</div>
<!-- 已完成 -->
<div v-else-if="aiSummary.status === 2 && aiSummary.summary" class="ai-summary-card">
@ -268,7 +268,7 @@
<!-- 摘要未就绪时提示 -->
<div v-else-if="aiSummary && aiSummary.status !== 2" class="ai-chat-unavail">
AI 摘要就绪后才能开始对话
{{ aiSummary.blockReason || 'AI 摘要就绪后才能开始对话' }}
</div>
</aside>
</div>
@ -338,6 +338,12 @@ interface FileTreeNode {
source: ReviewMeetingFileRespVO
}
interface ProjectAiState {
summary: ReviewAiSummaryVO | null
conversationId: number | null
messages: ChatMessageVO[]
}
// & state
const pageLoading = ref(false)
const previewLoading = ref(false)
@ -393,8 +399,10 @@ const streamingLoading = ref(false)
const aiMessagesRef = ref<HTMLElement>()
let streamAbortCtrl: AbortController | null = null
let summaryPollingTimer: ReturnType<typeof setTimeout> | null = null
let aiLoadToken = 0
let officeWarmupTimers: ReturnType<typeof setTimeout>[] = []
let previewFrameTimeoutTimer: ReturnType<typeof setTimeout> | null = null
const aiProjectCache = ref<Record<number, ProjectAiState>>({})
//
watch(keyword, (val) => treeRef.value?.filter(val.trim()))
@ -529,14 +537,24 @@ const handleNodeCollapse = (data: TreeNode) => {
const handleNodeClick = async (data: TreeNode) => {
if (data.type === 'project') {
persistCurrentProjectAiState()
const switched = activeProjectId.value !== data.projectId
activeProjectId.value = data.projectId
await loadProjectFiles(data)
if (switched) resetProjectAiState()
if (aiPanelOpen.value) await loadProjectAi(data.projectId)
if (switched && aiPanelOpen.value) {
restoreOrLoadProjectAi(data.projectId)
}
if (!switched && aiPanelOpen.value) {
restoreOrLoadProjectAi(data.projectId)
}
return
}
persistCurrentProjectAiState()
const switched = activeProjectId.value !== data.projectId
await selectFileNode(data)
if (aiPanelOpen.value && (switched || !aiSummary.value)) {
restoreOrLoadProjectAi(data.projectId)
}
}
//
@ -571,7 +589,7 @@ const loadCatalog = async () => {
const toggleAiPanel = async () => {
aiPanelOpen.value = !aiPanelOpen.value
if (aiPanelOpen.value && activeProjectId.value) {
await loadProjectAi(activeProjectId.value)
restoreOrLoadProjectAi(activeProjectId.value)
}
}
@ -585,11 +603,47 @@ const resetProjectAiState = () => {
streamingText.value = ''
}
const persistCurrentProjectAiState = () => {
const projectId = activeProjectId.value
if (!projectId) return
aiProjectCache.value[projectId] = {
summary: aiSummary.value,
conversationId: aiConversationId.value,
messages: [...aiMessages.value]
}
}
const applyProjectAiState = (state?: ProjectAiState) => {
resetProjectAiState()
aiSummary.value = state?.summary ?? null
aiConversationId.value = state?.conversationId ?? null
aiMessages.value = state?.messages ? [...state.messages] : []
}
const restoreOrLoadProjectAi = (projectId: number) => {
const cached = aiProjectCache.value[projectId]
if (cached) {
applyProjectAiState(cached)
if (cached.summary?.status === AI_SUMMARY_STATUS.BUILDING) {
startSummaryPolling(projectId)
} else if (cached.summary?.status !== AI_SUMMARY_STATUS.SUCCESS) {
loadProjectAi(projectId)
}
scrollAiToBottom()
return
}
loadProjectAi(projectId)
}
const loadProjectAi = async (projectId: number) => {
resetProjectAiState()
aiSummaryLoading.value = true
const token = ++aiLoadToken
try {
aiSummary.value = await getProjectAiSummary(projectId)
const summary = await getProjectAiSummary(projectId)
if (token !== aiLoadToken || activeProjectId.value !== projectId) return
aiSummary.value = summary
persistCurrentProjectAiState()
//
if (aiSummary.value?.status === AI_SUMMARY_STATUS.BUILDING) {
startSummaryPolling(projectId)
@ -608,11 +662,15 @@ const loadProjectAi = async (projectId: number) => {
const openAndLoadConversation = async (projectId: number) => {
aiChatLoading.value = true
const token = aiLoadToken
try {
const conv = await openProjectAiConversation(projectId)
if (token !== aiLoadToken || activeProjectId.value !== projectId) return
aiConversationId.value = conv.conversationId
const msgs = await ChatMessageApi.getChatMessageListByConversationId(conv.conversationId)
if (token !== aiLoadToken || activeProjectId.value !== projectId) return
aiMessages.value = msgs || []
persistCurrentProjectAiState()
await scrollAiToBottom()
} catch {
//
@ -628,6 +686,7 @@ const startSummaryPolling = (projectId: number) => {
if (activeProjectId.value !== projectId || !aiPanelOpen.value) return
try {
aiSummary.value = await getProjectAiSummary(projectId)
persistCurrentProjectAiState()
} catch { /* silent */ }
if (aiSummary.value?.status === AI_SUMMARY_STATUS.BUILDING) {
startSummaryPolling(projectId)
@ -679,6 +738,7 @@ const handleSend = async () => {
// send
aiMessages.value.pop()
aiMessages.value.push(data.send)
persistCurrentProjectAiState()
}
if (data?.receive?.content) {
streamingText.value += data.receive.content
@ -699,6 +759,7 @@ const handleSend = async () => {
createTime: new Date()
} as any)
streamingText.value = ''
persistCurrentProjectAiState()
}
streamingLoading.value = false
await scrollAiToBottom()
@ -719,6 +780,7 @@ const stopStream = () => {
createTime: new Date()
} as any)
streamingText.value = ''
persistCurrentProjectAiState()
}
streamingLoading.value = false
}
@ -729,6 +791,7 @@ const handleClearMessages = async () => {
stopStream()
await clearProjectAiMessages(activeProjectId.value)
aiMessages.value = []
persistCurrentProjectAiState()
ElMessage.success('问答记录已清空')
}