fix(review-tablet): 修复AI助手项目切换状态
parent
91b550fb1e
commit
ee816f95c7
|
|
@ -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('问答记录已清空')
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue