Skip to content

Commit c987ce9

Browse files
authored
Merge pull request #25 from Geniusay/master
问题渲染
2 parents 97b90b2 + 47e8630 commit c987ce9

9 files changed

Lines changed: 2808 additions & 73 deletions

File tree

prompto-lab-ui/src/components/Chat/AIChatPage.vue

Lines changed: 117 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@
3434
</div>
3535
</div>
3636

37-
<!-- 中间对话主页面 -->
37+
<!-- 中间问答主页面 -->
3838
<div class="main-content" :style="{ width: mainContentWidth + 'px' }">
39-
<ChatMain
40-
:messages="currentBranchMessages"
39+
<QuestionRenderer
40+
:current-question="currentQuestion"
4141
:is-loading="isLoading"
42-
:streaming-node-id="streamingNodeId"
4342
@send-message="handleSendMessage"
43+
@submit-answer="handleSubmitAnswer"
4444
/>
4545
</div>
4646

@@ -74,7 +74,7 @@
7474

7575
<script setup lang="ts">
7676
import { ref, computed, onMounted, onUnmounted } from 'vue'
77-
import ChatMain from './ChatMain.vue'
77+
import QuestionRenderer from './QuestionRenderer.vue'
7878
import ChatTree from './ChatTree.vue'
7979
import MindMapTree from './MindMapTree.vue'
8080
import { startConversation, sendMessage, connectSSE, closeSSE, type MessageRequest, type MessageResponse, type ConversationSession } from '@/services/conversationApi'
@@ -114,10 +114,6 @@ const isResizing = ref(false)
114114
const startX = ref(0)
115115
const startRightWidth = ref(0)
116116
117-
// 添加流式消息状态管理
118-
const streamingNodeId = ref<string>('')
119-
const streamingContent = ref<string>('')
120-
121117
// 会话状态
122118
const session = ref<ConversationSession | null>(null)
123119
const eventSource = ref<EventSource | null>(null)
@@ -129,6 +125,9 @@ const conversationTree = ref<Map<string, ConversationNode>>(new Map())
129125
const currentNodeId = ref<string>('')
130126
const isLoading = ref(false)
131127
128+
// 问题状态管理
129+
const currentQuestion = ref<any>(null)
130+
132131
// 初始化会话
133132
const initializeSession = async () => {
134133
if (isInitializing.value) return
@@ -184,70 +183,42 @@ const initializeSession = async () => {
184183
const handleSSEMessage = (response: MessageResponse) => {
185184
console.log('收到SSE消息:', response)
186185
186+
// 根据消息类型处理
187187
switch (response.type) {
188188
case 'AI_QUESTION':
189-
case 'AI_ANSWER':
189+
// 尝试解析问题内容为问题对象
190+
try {
191+
const questionData = JSON.parse(response.content)
192+
if (questionData.type && ['input', 'single', 'multi', 'form'].includes(questionData.type)) {
193+
currentQuestion.value = questionData
194+
isLoading.value = false
195+
break
196+
}
197+
} catch (e) {
198+
console.log('非JSON格式的问题,作为普通消息处理')
199+
}
200+
// 如果不是问题格式,作为普通消息处理
190201
addAIMessage(response.nodeId, response.content)
191202
break
192-
case 'AI_STREAM_START':
193-
// 开始流式响应
194-
startStreamingMessage(response.nodeId)
203+
case 'AI_ANSWER': // 添加对AI_ANSWER类型的处理
204+
addAIMessage(response.nodeId, response.content)
195205
break
196-
case 'AI_STREAM_CHUNK':
197-
// 接收流式内容片段
198-
appendStreamingContent(response.nodeId, response.content)
206+
case 'AI_SELECTION_QUESTION':
207+
addAISelectionMessage(response.nodeId, response.content, response.options || [])
199208
break
200-
case 'AI_STREAM_END':
201-
// 结束流式响应
202-
finishStreamingMessage(response.nodeId)
209+
case 'USER_ANSWER':
210+
// 用户消息确认,通常不需要特殊处理
211+
break
212+
case 'SYSTEM_INFO':
213+
toast.info({
214+
title: '系统消息',
215+
message: response.content,
216+
duration: 3000
217+
})
218+
break
219+
default:
220+
console.warn('未知的消息类型:', response.type, response)
203221
break
204-
// ... 其他case保持不变
205-
}
206-
}
207-
208-
// 开始流式消息
209-
const startStreamingMessage = (nodeId: string) => {
210-
streamingNodeId.value = nodeId
211-
streamingContent.value = ''
212-
213-
const aiNode: ConversationNode = {
214-
id: nodeId,
215-
content: '',
216-
type: 'assistant',
217-
timestamp: new Date(),
218-
parentId: currentNodeId.value,
219-
children: [],
220-
isActive: true
221-
}
222-
223-
const currentNode = conversationTree.value.get(currentNodeId.value)
224-
if (currentNode) {
225-
currentNode.children.push(nodeId)
226-
}
227-
228-
conversationTree.value.set(nodeId, aiNode)
229-
currentNodeId.value = nodeId
230-
isLoading.value = false
231-
}
232-
233-
// 追加流式内容
234-
const appendStreamingContent = (nodeId: string, chunk: string) => {
235-
if (streamingNodeId.value === nodeId) {
236-
streamingContent.value += chunk
237-
238-
// 更新节点内容
239-
const node = conversationTree.value.get(nodeId)
240-
if (node) {
241-
node.content = streamingContent.value
242-
}
243-
}
244-
}
245-
246-
// 完成流式消息
247-
const finishStreamingMessage = (nodeId: string) => {
248-
if (streamingNodeId.value === nodeId) {
249-
streamingNodeId.value = ''
250-
streamingContent.value = ''
251222
}
252223
}
253224
@@ -275,13 +246,7 @@ const handleSSEError = (error: Event) => {
275246
}
276247
277248
// 添加AI消息到对话树
278-
const addAIMessage = (nodeId: string, content: string, isStreaming = false) => {
279-
const existingNode = conversationTree.value.get(nodeId)
280-
if (existingNode && isStreaming) {
281-
// 如果是流式更新,只更新内容
282-
existingNode.content = content
283-
return
284-
}
249+
const addAIMessage = (nodeId: string, content: string) => {
285250
const aiNode: ConversationNode = {
286251
id: nodeId,
287252
content,
@@ -429,6 +394,9 @@ const handleSendMessage = async (content: string) => {
429394
return
430395
}
431396
397+
// 重置当前问题状态,进入新的对话
398+
currentQuestion.value = null
399+
432400
const userNodeId = `user_${Date.now()}`
433401
const userNode: ConversationNode = {
434402
id: userNodeId,
@@ -492,6 +460,82 @@ const handleSendMessage = async (content: string) => {
492460
}
493461
}
494462
463+
// 处理答案提交
464+
const handleSubmitAnswer = async (answerData: any) => {
465+
if (!session.value || !isConnected.value || !currentQuestion.value) {
466+
toast.error({
467+
title: '提交失败',
468+
message: '会话状态异常,请重新开始',
469+
duration: 3000
470+
})
471+
return
472+
}
473+
474+
isLoading.value = true
475+
476+
try {
477+
// 构建答案消息
478+
let answerContent = ''
479+
480+
switch (currentQuestion.value.type) {
481+
case 'input':
482+
answerContent = `回答:${answerData}`
483+
break
484+
case 'single':
485+
const selectedOption = currentQuestion.value.options.find((opt: any) => opt.id === answerData[0])
486+
answerContent = `选择:${selectedOption ? selectedOption.label : answerData[0]}`
487+
break
488+
case 'multi':
489+
const selectedOptions = currentQuestion.value.options.filter((opt: any) => answerData.includes(opt.id))
490+
answerContent = `选择:${selectedOptions.map((opt: any) => opt.label).join('')}`
491+
break
492+
case 'form':
493+
const formAnswers = answerData.map((item: any) => {
494+
const field = currentQuestion.value.fields.find((f: any) => f.id === item.id)
495+
if (field) {
496+
if (field.type === 'input') {
497+
return `${field.question}:${item.value[0]}`
498+
} else {
499+
const selectedOpts = field.options?.filter((opt: any) => item.value.includes(opt.id))
500+
return `${field.question}:${selectedOpts?.map((opt: any) => opt.label).join('') || item.value.join('')}`
501+
}
502+
}
503+
return `${item.id}:${item.value.join('')}`
504+
})
505+
answerContent = `表单回答:\n${formAnswers.join('\n')}`
506+
break
507+
}
508+
509+
// 发送答案到后端
510+
const messageRequest: MessageRequest = {
511+
sessionId: session.value.sessionId,
512+
content: answerContent,
513+
type: 'USER_ANSWER'
514+
}
515+
516+
await sendMessage(messageRequest)
517+
518+
// 清除当前问题状态
519+
currentQuestion.value = null
520+
521+
toast.success({
522+
title: '提交成功',
523+
message: '答案已提交,等待AI回复',
524+
duration: 2000
525+
})
526+
527+
} catch (error: any) {
528+
console.error('提交答案失败:', error)
529+
isLoading.value = false
530+
531+
toast.error({
532+
title: '提交失败',
533+
message: error.message || '答案提交失败,请重试',
534+
duration: 4000
535+
})
536+
}
537+
}
538+
495539
const setNodeAndDescendantsInactive = (nodeId: string) => {
496540
const node = conversationTree.value.get(nodeId)
497541
if (node) {

0 commit comments

Comments
 (0)