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