|
@@ -104,50 +104,24 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 如果没有数据,显示默认卡片 -->
|
|
<!-- 如果没有数据,显示默认卡片 -->
|
|
|
- <div v-if="functionCards.length === 0" class="function-card" @click="handleFunctionCard('桥梁结构设计问题')">
|
|
|
|
|
- <div class="card-header">
|
|
|
|
|
- <div class="card-icon">
|
|
|
|
|
- <img :src="bridgeIcon" alt="桥梁结构设计问题" class="card-icon-img">
|
|
|
|
|
- </div>
|
|
|
|
|
- <h4>桥梁结构设计问题</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-description">
|
|
|
|
|
- <p>各类桥梁结构设计,计算与分析</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="functionCards.length === 0" class="function-card" @click="handleFunctionCard('桥梁施工技术咨询')">
|
|
|
|
|
- <div class="card-header">
|
|
|
|
|
- <div class="card-icon">
|
|
|
|
|
- <img :src="constructionIcon" alt="施工技术咨询" class="card-icon-img">
|
|
|
|
|
- </div>
|
|
|
|
|
- <h4>施工技术咨询</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-description">
|
|
|
|
|
- <p>桥梁施工方法,工艺与技术要点</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="functionCards.length === 0" class="function-card" @click="handleFunctionCard('材料与力学问题')">
|
|
|
|
|
- <div class="card-header">
|
|
|
|
|
- <div class="card-icon">
|
|
|
|
|
- <img :src="materialIcon" alt="材料与力学问题" class="card-icon-img">
|
|
|
|
|
|
|
+ <template v-if="functionCards.length === 0">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(card, index) in defaultFunctionCards"
|
|
|
|
|
+ :key="`default-card-${index}`"
|
|
|
|
|
+ class="function-card"
|
|
|
|
|
+ @click="handleFunctionCard(card.title)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <div class="card-header">
|
|
|
|
|
+ <div class="card-icon">
|
|
|
|
|
+ <img :src="card.icon" :alt="card.title" class="card-icon-img">
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <h4>{{ card.title }}</h4>
|
|
|
</div>
|
|
</div>
|
|
|
- <h4>材料与力学问题</h4>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="card-description">
|
|
|
|
|
- <p>建筑材料性能与结构力学分析</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="functionCards.length === 0" class="function-card" @click="handleFunctionCard('规范标准查询')">
|
|
|
|
|
- <div class="card-header">
|
|
|
|
|
- <div class="card-icon">
|
|
|
|
|
- <img :src="standardIcon" alt="规范标准查询" class="card-icon-img">
|
|
|
|
|
|
|
+ <div class="card-description">
|
|
|
|
|
+ <p>{{ card.description }}</p>
|
|
|
</div>
|
|
</div>
|
|
|
- <h4>规范标准查询</h4>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="card-description">
|
|
|
|
|
- <p>行业规范,标准解读与应用</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ </template>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
@@ -433,18 +407,17 @@
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 如果没有数据,显示默认问题 -->
|
|
<!-- 如果没有数据,显示默认问题 -->
|
|
|
- <div v-if="hotQuestions.length === 0" class="question-tag" @click="handleRecommendedQuestion('施工安全生产责任的规定')">
|
|
|
|
|
- <img :src="questionIcon1" alt="问题" class="question-icon">
|
|
|
|
|
- 施工安全生产责任的规定
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="hotQuestions.length === 0" class="question-tag" @click="handleRecommendedQuestion('工程建设质量的要求')">
|
|
|
|
|
- <img :src="questionIcon2" alt="问题" class="question-icon">
|
|
|
|
|
- 工程建设质量的要求
|
|
|
|
|
- </div>
|
|
|
|
|
- <div v-if="hotQuestions.length === 0" class="question-tag" @click="handleRecommendedQuestion('公路桥梁加固设计规范')">
|
|
|
|
|
- <img :src="questionIcon3" alt="文档" class="question-icon">
|
|
|
|
|
- 公路桥梁加固设计规范
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
+ <template v-if="hotQuestions.length === 0">
|
|
|
|
|
+ <div
|
|
|
|
|
+ v-for="(question, index) in defaultHotQuestions"
|
|
|
|
|
+ :key="`default-${index}`"
|
|
|
|
|
+ class="question-tag"
|
|
|
|
|
+ @click="handleRecommendedQuestion(question)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <img :src="getQuestionIcon(question)" alt="问题" class="question-icon">
|
|
|
|
|
+ {{ question }}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </template>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 用户推荐问题区域 -->
|
|
<!-- 用户推荐问题区域 -->
|
|
@@ -707,15 +680,142 @@ const isOnlineModel = ref(true) // 是否使用在线大模型
|
|
|
// 切换模型类型
|
|
// 切换模型类型
|
|
|
const toggleModelType = () => {
|
|
const toggleModelType = () => {
|
|
|
isOnlineModel.value = !isOnlineModel.value
|
|
isOnlineModel.value = !isOnlineModel.value
|
|
|
- showToast(isOnlineModel.value ? '已切换至在线大模型 (glm-4-plus)' : '已切换至本地模型')
|
|
|
|
|
|
|
+ showToast(
|
|
|
|
|
+ isOnlineModel.value ? '启动在线大模型 (glm-4-plus)' : '已禁用在线大模型'
|
|
|
|
|
+ )
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 当前激活的模块模式:'ai-qa', 'ai-writing', 'safety-training', 'exam-workshop'
|
|
|
|
|
const currentMode = ref('ai-qa')
|
|
const currentMode = ref('ai-qa')
|
|
|
|
|
+const hasUserSelectedMode = ref(false)
|
|
|
|
|
+
|
|
|
|
|
+const defaultHotQuestionsByMode = {
|
|
|
|
|
+ 'ai-qa': [
|
|
|
|
|
+ '施工安全生产责任的规定',
|
|
|
|
|
+ '工程建设质量的要求',
|
|
|
|
|
+ '公路桥梁加固设计规范'
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'ai-writing': [
|
|
|
|
|
+ '生成施工安全培训通知',
|
|
|
|
|
+ '编写安全生产责任制范本',
|
|
|
|
|
+ '生成隐患排查整改方案'
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'safety-training': [
|
|
|
|
|
+ '新员工安全培训内容有哪些',
|
|
|
|
|
+ '特种作业人员安全培训要求',
|
|
|
|
|
+ '安全培训档案应包含哪些内容'
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'exam-workshop': [
|
|
|
|
|
+ '生成安全培训考试题库',
|
|
|
|
|
+ '安全生产法规知识点整理',
|
|
|
|
|
+ '常见隐患排查考试题'
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const defaultHotQuestions = computed(
|
|
|
|
|
+ () => defaultHotQuestionsByMode[currentMode.value] || defaultHotQuestionsByMode['ai-qa']
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const defaultFunctionCardsByMode = {
|
|
|
|
|
+ 'ai-qa': [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '桥梁结构设计问题',
|
|
|
|
|
+ description: '各类桥梁结构设计,计算与分析',
|
|
|
|
|
+ icon: bridgeIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '施工技术咨询',
|
|
|
|
|
+ description: '桥梁施工方法,工艺与技术要点',
|
|
|
|
|
+ icon: constructionIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '材料与力学问题',
|
|
|
|
|
+ description: '建筑材料性能与结构力学分析',
|
|
|
|
|
+ icon: materialIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '规范标准查询',
|
|
|
|
|
+ description: '行业规范,标准解读与应用',
|
|
|
|
|
+ icon: standardIcon
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'ai-writing': [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '安全培训通知',
|
|
|
|
|
+ description: '生成培训通知、计划与执行安排',
|
|
|
|
|
+ icon: bridgeIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '制度与方案编写',
|
|
|
|
|
+ description: '生成制度、方案、流程与模板',
|
|
|
|
|
+ icon: constructionIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '隐患整改方案',
|
|
|
|
|
+ description: '生成整改措施与闭环流程',
|
|
|
|
|
+ icon: materialIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '会议纪要模板',
|
|
|
|
|
+ description: '生成会议纪要与行动项清单',
|
|
|
|
|
+ icon: standardIcon
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'safety-training': [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '新员工安全培训',
|
|
|
|
|
+ description: '入职培训要点与必修内容',
|
|
|
|
|
+ icon: bridgeIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '特种作业培训',
|
|
|
|
|
+ description: '特种作业上岗培训与复训要求',
|
|
|
|
|
+ icon: constructionIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '安全教育制度',
|
|
|
|
|
+ description: '安全教育制度与实施流程',
|
|
|
|
|
+ icon: materialIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '培训档案管理',
|
|
|
|
|
+ description: '培训记录与档案规范管理',
|
|
|
|
|
+ icon: standardIcon
|
|
|
|
|
+ }
|
|
|
|
|
+ ],
|
|
|
|
|
+ 'exam-workshop': [
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '考试题库生成',
|
|
|
|
|
+ description: '按主题生成题库与解析',
|
|
|
|
|
+ icon: bridgeIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '法规知识点',
|
|
|
|
|
+ description: '法规标准要点梳理与归纳',
|
|
|
|
|
+ icon: constructionIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '隐患排查题',
|
|
|
|
|
+ description: '隐患识别与处置题型',
|
|
|
|
|
+ icon: materialIcon
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ title: '考试大纲',
|
|
|
|
|
+ description: '生成考试大纲与评分标准',
|
|
|
|
|
+ icon: standardIcon
|
|
|
|
|
+ }
|
|
|
|
|
+ ]
|
|
|
|
|
+}
|
|
|
|
|
|
|
|
-// 切换到对应模块
|
|
|
|
|
-const setMode = (mode) => {
|
|
|
|
|
- if (currentMode.value === mode) {
|
|
|
|
|
|
|
+const defaultFunctionCards = computed(
|
|
|
|
|
+ () => defaultFunctionCardsByMode[currentMode.value] || defaultFunctionCardsByMode['ai-qa']
|
|
|
|
|
+)
|
|
|
|
|
+
|
|
|
|
|
+const setMode = (mode, options = {}) => {
|
|
|
|
|
+ const { allowToggle = true, source = 'user' } = options
|
|
|
|
|
+ if (source === 'user') {
|
|
|
|
|
+ hasUserSelectedMode.value = true
|
|
|
|
|
+ }
|
|
|
|
|
+ if (allowToggle && currentMode.value === mode) {
|
|
|
currentMode.value = 'ai-qa' // 再次点击取消选中,回到默认问答
|
|
currentMode.value = 'ai-qa' // 再次点击取消选中,回到默认问答
|
|
|
} else {
|
|
} else {
|
|
|
currentMode.value = mode
|
|
currentMode.value = mode
|
|
@@ -1450,7 +1550,7 @@ const getConversationMessages = async (conversationId) => {
|
|
|
.map(r => r.category)
|
|
.map(r => r.category)
|
|
|
|
|
|
|
|
categories.forEach(category => {
|
|
categories.forEach(category => {
|
|
|
- categoryExpandStates.value[index][category] = true
|
|
|
|
|
|
|
+ categoryExpandStates.value[index][category] = false
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1643,6 +1743,27 @@ const clearNewConversationState = () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 根据当前模式分发发送请求
|
|
|
|
|
+const submitQuestionByCurrentMode = async (question, options = {}) => {
|
|
|
|
|
+ const { windowSize = 3, nResults = 10 } = options
|
|
|
|
|
+ const normalizedQuestion = typeof question === 'string' ? question.trim() : ''
|
|
|
|
|
+ if (!normalizedQuestion) return
|
|
|
|
|
+
|
|
|
|
|
+ if (currentMode.value === 'ai-writing' || currentMode.value === 'safety-training') {
|
|
|
|
|
+ await handleNonStreamingSubmit({
|
|
|
|
|
+ question: normalizedQuestion,
|
|
|
|
|
+ businessType: currentMode.value === 'ai-writing' ? 2 : 1
|
|
|
|
|
+ })
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ await handleReportGeneratorSubmit({
|
|
|
|
|
+ question: normalizedQuestion,
|
|
|
|
|
+ windowSize,
|
|
|
|
|
+ nResults
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 发送消息
|
|
// 发送消息
|
|
|
const handleSendMessage = async () => {
|
|
const handleSendMessage = async () => {
|
|
|
if (!messageText.value.trim() || isSending.value) return
|
|
if (!messageText.value.trim() || isSending.value) return
|
|
@@ -1654,19 +1775,11 @@ const handleSendMessage = async () => {
|
|
|
showChat.value = true
|
|
showChat.value = true
|
|
|
|
|
|
|
|
clearNewConversationState()
|
|
clearNewConversationState()
|
|
|
-
|
|
|
|
|
- if (currentMode.value === 'ai-writing' || currentMode.value === 'safety-training') {
|
|
|
|
|
- await handleNonStreamingSubmit({
|
|
|
|
|
- question: messageText.value,
|
|
|
|
|
- businessType: currentMode.value === 'ai-writing' ? 2 : 1
|
|
|
|
|
- })
|
|
|
|
|
- } else {
|
|
|
|
|
- await handleReportGeneratorSubmit({
|
|
|
|
|
- question: messageText.value,
|
|
|
|
|
- windowSize: 3,
|
|
|
|
|
- nResults: 10
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
|
|
+
|
|
|
|
|
+ await submitQuestionByCurrentMode(messageText.value, {
|
|
|
|
|
+ windowSize: 3,
|
|
|
|
|
+ nResults: 10
|
|
|
|
|
+ })
|
|
|
|
|
|
|
|
messageText.value = ''
|
|
messageText.value = ''
|
|
|
clearRecommendQuestions()
|
|
clearRecommendQuestions()
|
|
@@ -1680,6 +1793,10 @@ const handleSendMessage = async () => {
|
|
|
|
|
|
|
|
// 处理非流式请求 (AI写作 和 安全培训)
|
|
// 处理非流式请求 (AI写作 和 安全培训)
|
|
|
const handleNonStreamingSubmit = async (data) => {
|
|
const handleNonStreamingSubmit = async (data) => {
|
|
|
|
|
+ if (!isSending.value) {
|
|
|
|
|
+ isSending.value = true
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
currentQuestion.value = data.question
|
|
currentQuestion.value = data.question
|
|
|
|
|
|
|
|
// 添加用户消息
|
|
// 添加用户消息
|
|
@@ -1705,26 +1822,43 @@ const handleNonStreamingSubmit = async (data) => {
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
const response = await apis.sendDeepseekMessage({
|
|
const response = await apis.sendDeepseekMessage({
|
|
|
- user_question: data.question,
|
|
|
|
|
|
|
+ message: data.question,
|
|
|
business_type: data.businessType,
|
|
business_type: data.businessType,
|
|
|
enable_online_model: isOnlineModel.value,
|
|
enable_online_model: isOnlineModel.value,
|
|
|
- ai_conversation_id: ai_conversation_id.value
|
|
|
|
|
|
|
+ conversation_id: ai_conversation_id.value
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
if (response.statusCode === 200) {
|
|
if (response.statusCode === 200) {
|
|
|
const aiMessage = chatMessages.value[aiMessageIndex]
|
|
const aiMessage = chatMessages.value[aiMessageIndex]
|
|
|
|
|
+ const responseData = response?.data || {}
|
|
|
|
|
+ const aiReply =
|
|
|
|
|
+ responseData.reply ||
|
|
|
|
|
+ responseData.response ||
|
|
|
|
|
+ responseData.content ||
|
|
|
|
|
+ response.reply ||
|
|
|
|
|
+ response.content ||
|
|
|
|
|
+ ''
|
|
|
|
|
+ const conversationId =
|
|
|
|
|
+ responseData.ai_conversation_id ||
|
|
|
|
|
+ responseData.conversation_id ||
|
|
|
|
|
+ response.ai_conversation_id ||
|
|
|
|
|
+ response.conversation_id
|
|
|
|
|
|
|
|
// 如果用户已经点击了停止,则不再继续输出
|
|
// 如果用户已经点击了停止,则不再继续输出
|
|
|
if (aiMessage._stopped) {
|
|
if (aiMessage._stopped) {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ if (!aiReply || typeof aiReply !== 'string') {
|
|
|
|
|
+ throw new Error('AI返回内容为空')
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
aiMessage.isTyping = false
|
|
aiMessage.isTyping = false
|
|
|
- aiMessage.content = response.data
|
|
|
|
|
|
|
+ aiMessage.content = aiReply
|
|
|
|
|
|
|
|
// 更新 conversation ID
|
|
// 更新 conversation ID
|
|
|
- if (response.ai_conversation_id) {
|
|
|
|
|
- ai_conversation_id.value = response.ai_conversation_id
|
|
|
|
|
|
|
+ if (conversationId) {
|
|
|
|
|
+ ai_conversation_id.value = conversationId
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 添加打字机效果显示
|
|
// 添加打字机效果显示
|
|
@@ -1741,10 +1875,10 @@ const handleNonStreamingSubmit = async (data) => {
|
|
|
</div>
|
|
</div>
|
|
|
</div>`
|
|
</div>`
|
|
|
// 实际内容保存起来,点击查看详情时可以使用
|
|
// 实际内容保存起来,点击查看详情时可以使用
|
|
|
- aiMessage.fullContent = response.data
|
|
|
|
|
|
|
+ aiMessage.fullContent = aiReply
|
|
|
} else {
|
|
} else {
|
|
|
// AI写作等: 正常打字机输出
|
|
// AI写作等: 正常打字机输出
|
|
|
- startTypewriterEffect(aiMessage, response.data, 30)
|
|
|
|
|
|
|
+ startTypewriterEffect(aiMessage, aiReply, 30)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 刷新历史记录
|
|
// 刷新历史记录
|
|
@@ -1790,22 +1924,76 @@ const getQuestionIcon = (question) => {
|
|
|
return icon
|
|
return icon
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const getModeRecommendationType = (mode) => {
|
|
|
|
|
+ if (mode === 'ai-writing') return 2
|
|
|
|
|
+ if (mode === 'safety-training') return 1
|
|
|
|
|
+ if (mode === 'exam-workshop') return 3
|
|
|
|
|
+ return 0
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const pickScopedRecommendationItems = (items, expectedType, typeField) => {
|
|
|
|
|
+ if (!Array.isArray(items)) return []
|
|
|
|
|
+ if (expectedType === 0) return items
|
|
|
|
|
+
|
|
|
|
|
+ const hasTypeField = items.some(
|
|
|
|
|
+ item => item && Object.prototype.hasOwnProperty.call(item, typeField)
|
|
|
|
|
+ )
|
|
|
|
|
+ if (!hasTypeField) {
|
|
|
|
|
+ return null
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return items.filter(item => Number(item?.[typeField]) === expectedType)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
// 点击功能卡片
|
|
// 点击功能卡片
|
|
|
// ReportGenerator相关方法
|
|
// ReportGenerator相关方法
|
|
|
const handleCategoryToggle = (messageIndex, data) => {
|
|
const handleCategoryToggle = (messageIndex, data) => {
|
|
|
if (!categoryExpandStates.value[messageIndex]) {
|
|
if (!categoryExpandStates.value[messageIndex]) {
|
|
|
categoryExpandStates.value[messageIndex] = {}
|
|
categoryExpandStates.value[messageIndex] = {}
|
|
|
}
|
|
}
|
|
|
- categoryExpandStates.value[messageIndex][data.category] = data.expanded
|
|
|
|
|
|
|
+ const matchedCategory = findCategoryStateKey(messageIndex, data.category)
|
|
|
|
|
+ categoryExpandStates.value[messageIndex][matchedCategory || data.category] = data.expanded
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const normalizeCategoryName = (category) => {
|
|
|
|
|
+ return typeof category === 'string' ? category.trim() : ''
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const findCategoryStateKey = (messageIndex, category) => {
|
|
|
|
|
+ const stateMap = categoryExpandStates.value[messageIndex]
|
|
|
|
|
+ if (!stateMap) return ''
|
|
|
|
|
+
|
|
|
|
|
+ const normalizedCategory = normalizeCategoryName(category)
|
|
|
|
|
+ if (!normalizedCategory) return ''
|
|
|
|
|
+
|
|
|
|
|
+ if (Object.prototype.hasOwnProperty.call(stateMap, category)) {
|
|
|
|
|
+ return category
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const keys = Object.keys(stateMap)
|
|
|
|
|
+ const exactKey = keys.find(key => normalizeCategoryName(key) === normalizedCategory)
|
|
|
|
|
+ if (exactKey) return exactKey
|
|
|
|
|
+
|
|
|
|
|
+ return keys.find(key => {
|
|
|
|
|
+ const normalizedKey = normalizeCategoryName(key)
|
|
|
|
|
+ return normalizedKey && (
|
|
|
|
|
+ normalizedKey.includes(normalizedCategory) ||
|
|
|
|
|
+ normalizedCategory.includes(normalizedKey)
|
|
|
|
|
+ )
|
|
|
|
|
+ }) || ''
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const isCategoryExpanded = (messageIndex, category) => {
|
|
const isCategoryExpanded = (messageIndex, category) => {
|
|
|
if (!category) return true
|
|
if (!category) return true
|
|
|
if (!categoryExpandStates.value[messageIndex]) {
|
|
if (!categoryExpandStates.value[messageIndex]) {
|
|
|
categoryExpandStates.value[messageIndex] = {}
|
|
categoryExpandStates.value[messageIndex] = {}
|
|
|
- return true
|
|
|
|
|
|
|
+ return false
|
|
|
}
|
|
}
|
|
|
- return categoryExpandStates.value[messageIndex][category] !== false
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const matchedCategory = findCategoryStateKey(messageIndex, category)
|
|
|
|
|
+ if (!matchedCategory) return false
|
|
|
|
|
+
|
|
|
|
|
+ return categoryExpandStates.value[messageIndex][matchedCategory] === true
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 处理安全培训文档点击
|
|
// 处理安全培训文档点击
|
|
@@ -2076,7 +2264,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
if (!categoryExpandStates.value[aiMessageIndex]) {
|
|
if (!categoryExpandStates.value[aiMessageIndex]) {
|
|
|
categoryExpandStates.value[aiMessageIndex] = {}
|
|
categoryExpandStates.value[aiMessageIndex] = {}
|
|
|
}
|
|
}
|
|
|
- categoryExpandStates.value[aiMessageIndex][data.category] = true
|
|
|
|
|
|
|
+ categoryExpandStates.value[aiMessageIndex][data.category] = false
|
|
|
|
|
|
|
|
// 保存当前分类名称,用于后续报告匹配
|
|
// 保存当前分类名称,用于后续报告匹配
|
|
|
aiMessage.currentCategory = data.category
|
|
aiMessage.currentCategory = data.category
|
|
@@ -2100,7 +2288,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
similarity: data.similarity,
|
|
similarity: data.similarity,
|
|
|
metadata: {
|
|
metadata: {
|
|
|
...data.metadata,
|
|
...data.metadata,
|
|
|
- _displayCategory: aiMessage.currentCategory // 存储当前显示的分类名
|
|
|
|
|
|
|
+ _displayCategory: data.metadata?.primary_category || aiMessage.currentCategory // 存储当前显示的分类名
|
|
|
},
|
|
},
|
|
|
report: {
|
|
report: {
|
|
|
display_name: '',
|
|
display_name: '',
|
|
@@ -2140,7 +2328,9 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
|
|
|
|
|
let targetReport
|
|
let targetReport
|
|
|
if (idx !== undefined) {
|
|
if (idx !== undefined) {
|
|
|
- const displayCategory = aiMessage.reports[idx].metadata?._displayCategory
|
|
|
|
|
|
|
+ const displayCategory = reportData.metadata?.primary_category ||
|
|
|
|
|
+ aiMessage.reports[idx].metadata?._displayCategory ||
|
|
|
|
|
+ aiMessage.currentCategory
|
|
|
const fullSummary = reportData.report?.summary || ''
|
|
const fullSummary = reportData.report?.summary || ''
|
|
|
const fullAnalysis = reportData.report?.analysis || ''
|
|
const fullAnalysis = reportData.report?.analysis || ''
|
|
|
const fullClauses = reportData.report?.clauses || ''
|
|
const fullClauses = reportData.report?.clauses || ''
|
|
@@ -2158,7 +2348,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
status: 'completed',
|
|
status: 'completed',
|
|
|
metadata: {
|
|
metadata: {
|
|
|
...reportData.metadata, // 保留所有metadata字段
|
|
...reportData.metadata, // 保留所有metadata字段
|
|
|
- _displayCategory: displayCategory || aiMessage.currentCategory
|
|
|
|
|
|
|
+ _displayCategory: displayCategory
|
|
|
},
|
|
},
|
|
|
_fullContent: {
|
|
_fullContent: {
|
|
|
display_name: fullDisplayName,
|
|
display_name: fullDisplayName,
|
|
@@ -2186,7 +2376,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
|
|
|
status: 'completed',
|
|
status: 'completed',
|
|
|
metadata: {
|
|
metadata: {
|
|
|
...reportData.metadata, // 保留所有metadata字段
|
|
...reportData.metadata, // 保留所有metadata字段
|
|
|
- _displayCategory: aiMessage.currentCategory
|
|
|
|
|
|
|
+ _displayCategory: reportData.metadata?.primary_category || aiMessage.currentCategory
|
|
|
},
|
|
},
|
|
|
_fullContent: {
|
|
_fullContent: {
|
|
|
display_name: fullDisplayName,
|
|
display_name: fullDisplayName,
|
|
@@ -2759,39 +2949,41 @@ const handleReportGeneratorSubmit = async (data) => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const handleFunctionCard = (cardType) => {
|
|
|
|
|
|
|
+const handleFunctionCard = async (cardType) => {
|
|
|
clearAllTypeIntervals()
|
|
clearAllTypeIntervals()
|
|
|
chatMessages.value = []
|
|
chatMessages.value = []
|
|
|
ai_conversation_id.value = 0
|
|
ai_conversation_id.value = 0
|
|
|
showChat.value = true
|
|
showChat.value = true
|
|
|
|
|
|
|
|
- handleReportGeneratorSubmit({
|
|
|
|
|
- question: `请详细介绍${cardType}的相关内容`,
|
|
|
|
|
|
|
+ await submitQuestionByCurrentMode(`请详细介绍${cardType}的相关内容`, {
|
|
|
windowSize: 3,
|
|
windowSize: 3,
|
|
|
nResults: 10
|
|
nResults: 10
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 点击推荐问题
|
|
// 点击推荐问题
|
|
|
-const handleRecommendedQuestion = (question) => {
|
|
|
|
|
|
|
+const handleRecommendedQuestion = async (question) => {
|
|
|
clearAllTypeIntervals()
|
|
clearAllTypeIntervals()
|
|
|
chatMessages.value = []
|
|
chatMessages.value = []
|
|
|
ai_conversation_id.value = 0
|
|
ai_conversation_id.value = 0
|
|
|
showChat.value = true
|
|
showChat.value = true
|
|
|
|
|
|
|
|
- handleReportGeneratorSubmit({
|
|
|
|
|
- question: question,
|
|
|
|
|
|
|
+ await submitQuestionByCurrentMode(question, {
|
|
|
windowSize: 3,
|
|
windowSize: 3,
|
|
|
nResults: 10
|
|
nResults: 10
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 点击用户推荐问题
|
|
// 点击用户推荐问题
|
|
|
-const handleUserRecommendQuestion = (question) => {
|
|
|
|
|
|
|
+const handleUserRecommendQuestion = async (question) => {
|
|
|
clearRecommendQuestions()
|
|
clearRecommendQuestions()
|
|
|
|
|
+ showChat.value = true
|
|
|
|
|
+
|
|
|
|
|
+ if (chatMessages.value.length === 0) {
|
|
|
|
|
+ clearNewConversationState()
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- handleReportGeneratorSubmit({
|
|
|
|
|
- question: question,
|
|
|
|
|
|
|
+ await submitQuestionByCurrentMode(question, {
|
|
|
windowSize: 3,
|
|
windowSize: 3,
|
|
|
nResults: 10
|
|
nResults: 10
|
|
|
})
|
|
})
|
|
@@ -3507,16 +3699,29 @@ const removeSelectedFile = () => {
|
|
|
const getFunctionCards = async () => {
|
|
const getFunctionCards = async () => {
|
|
|
try {
|
|
try {
|
|
|
console.log('开始获取功能卡片...')
|
|
console.log('开始获取功能卡片...')
|
|
|
- let functionType = 0; // AI问答
|
|
|
|
|
- if (currentMode.value === 'ai-writing') functionType = 2;
|
|
|
|
|
- if (currentMode.value === 'safety-training') functionType = 1;
|
|
|
|
|
- if (currentMode.value === 'exam-workshop') functionType = 3;
|
|
|
|
|
|
|
+ const requestMode = currentMode.value
|
|
|
|
|
+ const functionType = getModeRecommendationType(requestMode)
|
|
|
|
|
|
|
|
const response = await apis.getFunctionCard({ function_type: functionType })
|
|
const response = await apis.getFunctionCard({ function_type: functionType })
|
|
|
console.log('功能卡片响应:', response)
|
|
console.log('功能卡片响应:', response)
|
|
|
|
|
|
|
|
if (response.statusCode === 200) {
|
|
if (response.statusCode === 200) {
|
|
|
- functionCards.value = response.data
|
|
|
|
|
|
|
+ if (currentMode.value !== requestMode) {
|
|
|
|
|
+ console.log('模式已切换,忽略旧的功能卡片结果:', requestMode, '->', currentMode.value)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const scopedCards = pickScopedRecommendationItems(
|
|
|
|
|
+ response.data,
|
|
|
|
|
+ functionType,
|
|
|
|
|
+ 'function_type'
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (scopedCards === null) {
|
|
|
|
|
+ console.warn('功能卡片接口未返回类型字段,当前模式继续使用默认卡片:', requestMode)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ functionCards.value = scopedCards
|
|
|
console.log('功能卡片数据已设置:', functionCards.value)
|
|
console.log('功能卡片数据已设置:', functionCards.value)
|
|
|
} else {
|
|
} else {
|
|
|
console.error('获取功能卡片失败:', response.statusCode)
|
|
console.error('获取功能卡片失败:', response.statusCode)
|
|
@@ -3530,16 +3735,29 @@ const getFunctionCards = async () => {
|
|
|
const getHotQuestions = async () => {
|
|
const getHotQuestions = async () => {
|
|
|
try {
|
|
try {
|
|
|
console.log('开始获取热点问题...')
|
|
console.log('开始获取热点问题...')
|
|
|
- let questionType = 0; // AI问答
|
|
|
|
|
- if (currentMode.value === 'ai-writing') questionType = 2;
|
|
|
|
|
- if (currentMode.value === 'safety-training') questionType = 1;
|
|
|
|
|
- if (currentMode.value === 'exam-workshop') questionType = 3;
|
|
|
|
|
|
|
+ const requestMode = currentMode.value
|
|
|
|
|
+ const questionType = getModeRecommendationType(requestMode)
|
|
|
|
|
|
|
|
const response = await apis.getHotQuestion({ question_type: questionType })
|
|
const response = await apis.getHotQuestion({ question_type: questionType })
|
|
|
console.log('热点问题响应:', response)
|
|
console.log('热点问题响应:', response)
|
|
|
|
|
|
|
|
if (response.statusCode === 200) {
|
|
if (response.statusCode === 200) {
|
|
|
- hotQuestions.value = response.data
|
|
|
|
|
|
|
+ if (currentMode.value !== requestMode) {
|
|
|
|
|
+ console.log('模式已切换,忽略旧的热点问题结果:', requestMode, '->', currentMode.value)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ const scopedQuestions = pickScopedRecommendationItems(
|
|
|
|
|
+ response.data,
|
|
|
|
|
+ questionType,
|
|
|
|
|
+ 'question_type'
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ if (scopedQuestions === null) {
|
|
|
|
|
+ console.warn('热点问题接口未返回类型字段,当前模式继续使用默认问题:', requestMode)
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ hotQuestions.value = scopedQuestions
|
|
|
console.log('热点问题数据已设置:', hotQuestions.value)
|
|
console.log('热点问题数据已设置:', hotQuestions.value)
|
|
|
} else {
|
|
} else {
|
|
|
console.error('获取热点问题失败:', response.statusCode)
|
|
console.error('获取热点问题失败:', response.statusCode)
|
|
@@ -3551,6 +3769,8 @@ const getHotQuestions = async () => {
|
|
|
|
|
|
|
|
// 监听模式变化,重新获取卡片和问题
|
|
// 监听模式变化,重新获取卡片和问题
|
|
|
watch(currentMode, () => {
|
|
watch(currentMode, () => {
|
|
|
|
|
+ functionCards.value = []
|
|
|
|
|
+ hotQuestions.value = []
|
|
|
getFunctionCards()
|
|
getFunctionCards()
|
|
|
getHotQuestions()
|
|
getHotQuestions()
|
|
|
})
|
|
})
|
|
@@ -4115,9 +4335,8 @@ const autoSendMessage = async (message) => {
|
|
|
aiRelatedQuestions.value = []
|
|
aiRelatedQuestions.value = []
|
|
|
relatedQuestionsMessageId.value = null
|
|
relatedQuestionsMessageId.value = null
|
|
|
|
|
|
|
|
- // 使用ReportGenerator的提交逻辑(与直接发送消息一致)
|
|
|
|
|
- await handleReportGeneratorSubmit({
|
|
|
|
|
- question: message,
|
|
|
|
|
|
|
+ // 与直接发送一致:按当前模式分发到对应接口
|
|
|
|
|
+ await submitQuestionByCurrentMode(message, {
|
|
|
windowSize: 3,
|
|
windowSize: 3,
|
|
|
nResults: 10
|
|
nResults: 10
|
|
|
})
|
|
})
|
|
@@ -4710,10 +4929,12 @@ onMounted(async () => {
|
|
|
console.log('🎉 AI问答页面初始化完成')
|
|
console.log('🎉 AI问答页面初始化完成')
|
|
|
|
|
|
|
|
// 检查是否带有指定的模式
|
|
// 检查是否带有指定的模式
|
|
|
- const targetMode = route.query.mode
|
|
|
|
|
|
|
+ const targetMode = Array.isArray(route.query.mode) ? route.query.mode[0] : route.query.mode
|
|
|
if (targetMode && ['ai-qa', 'ai-writing', 'safety-training', 'exam-workshop'].includes(targetMode)) {
|
|
if (targetMode && ['ai-qa', 'ai-writing', 'safety-training', 'exam-workshop'].includes(targetMode)) {
|
|
|
console.log('检测到目标模式:', targetMode)
|
|
console.log('检测到目标模式:', targetMode)
|
|
|
- setMode(targetMode)
|
|
|
|
|
|
|
+ if (!hasUserSelectedMode.value && currentMode.value !== targetMode) {
|
|
|
|
|
+ setMode(targetMode, { allowToggle: false, source: 'system' })
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
// 立即清除URL中的mode参数,防止刷新时问题
|
|
// 立即清除URL中的mode参数,防止刷新时问题
|
|
|
router.replace({
|
|
router.replace({
|