修改代码,去掉原有组件的请求后端的方式。改用更简单的EventSource的方式。确保结果渲染正确。

This commit is contained in:
2025-10-22 22:40:19 +08:00
parent 108d755166
commit 7dbb8a3a08

View File

@@ -5,7 +5,7 @@ import type {
ConversationsProps, ConversationsProps,
PromptsProps, PromptsProps,
} from 'ant-design-x-vue' } from 'ant-design-x-vue'
import type { VNode } from 'vue' import type { Component, VNode } from 'vue'
import { import {
CloudUploadOutlined, CloudUploadOutlined,
CommentOutlined, CommentOutlined,
@@ -18,7 +18,7 @@ import {
ShareAltOutlined, ShareAltOutlined,
SmileOutlined, SmileOutlined,
} from '@ant-design/icons-vue' } from '@ant-design/icons-vue'
import { Badge, Button, Flex, Space, Typography, theme } from 'ant-design-vue' import { Badge, Button, Flex, Space, theme } from 'ant-design-vue'
import { import {
Attachments, Attachments,
Bubble, Bubble,
@@ -213,7 +213,7 @@ let messageIdCounter = 0
// ==================== Runtime ==================== // ==================== Runtime ====================
async function sendMessage(userMessage: string) { async function sendMessage(userMessage: string) {
console.log('📝 [Submit] 用户提交消息:', userMessage) console.log('[Submit] 用户提交消息:', userMessage)
if (!userMessage) return if (!userMessage) return
// 添加用户消息 // 添加用户消息
@@ -243,10 +243,10 @@ async function sendMessage(userMessage: string) {
message: userMessage, message: userMessage,
conversionId: convId, conversionId: convId,
} }
console.log('📤 [Request] 请求体:', JSON.stringify(requestBody, null, 2)) console.log('[Request] 请求体:', JSON.stringify(requestBody, null, 2))
// 调用后端接口 // 调用后端接口 - POST 请求 SSE 流式响应
const response = await fetch('http://localhost:8080/dashscope/chat', { const response = await fetch('http://localhost:8080/dashscope/generateStream', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@@ -254,7 +254,7 @@ async function sendMessage(userMessage: string) {
body: JSON.stringify(requestBody), body: JSON.stringify(requestBody),
}) })
console.log('📥 [Response] 响应状态:', response.status, response.statusText) console.log('[Response] 响应状态:', response.status, response.statusText)
if (!response.ok) { if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`) throw new Error(`请求失败: ${response.status} ${response.statusText}`)
@@ -266,25 +266,22 @@ async function sendMessage(userMessage: string) {
const decoder = new TextDecoder() const decoder = new TextDecoder()
let buffer = '' let buffer = ''
console.log('🌊 [Stream] 开始读取流数据...') console.log('[Stream] 开始读取流数据...')
try { try {
let isFirstChunk = true
while (true) { while (true) {
const { done, value } = await reader.read() const { done, value } = await reader.read()
if (done) { if (done) {
console.log('[Stream] 流读取完成') console.log('[Stream] 流读取完成')
const msg = messages.value[aiMsgIndex]
if (msg) {
msg.status = 'success'
}
break break
} }
// 解码数据 // 解码数据
const text = decoder.decode(value, { stream: true }) const text = decoder.decode(value, { stream: true })
buffer += text buffer += text
console.log('📦 [Stream] 收到原始数据:', text) console.log('[Stream] 收到原始数据:', text)
// 解析 SSE 格式: data: content\n\n // 解析 SSE 格式: data: content\n\n
const lines = buffer.split('\n\n') const lines = buffer.split('\n\n')
@@ -299,14 +296,28 @@ async function sendMessage(userMessage: string) {
for (const block of lines) { for (const block of lines) {
if (block.startsWith('data:')) { if (block.startsWith('data:')) {
// 提取 data: 后面的内容 // 提取 data: 后面的内容
const content = block.substring(5) const data = block.substring(5).trim()
console.log('[Parse] 解析后的内容:', JSON.stringify(content)) console.log('[Parse] 接收到数据:', data)
// 直接累积到消息内容 if (data) { // 若响应数据不为空
const msg = messages.value[aiMsgIndex] // 解析 JSON
if (msg) { const response = JSON.parse(data)
msg.content += content console.log('[Parse] 解析后的内容:', JSON.stringify(response))
console.log('🔄 [Update] 更新消息, 当前长度:', msg.content.length)
// 更新消息内容并强制触发响应式更新
const msg = messages.value[aiMsgIndex]
if (msg) {
// 第一次接收到数据时,将状态改为 success这样内容才会显示
if (isFirstChunk) {
isFirstChunk = false
console.log('[Stream] 首次接收数据,设置状态为 success')
}
// 持续追加流式回答
msg.content += response.v
msg.status = 'success'
console.log('[Update] 更新消息, 当前长度:', msg.content.length)
}
} }
} }
} }
@@ -316,7 +327,7 @@ async function sendMessage(userMessage: string) {
} }
} }
} catch (error) { } catch (error) {
console.error('[Error] 请求错误:', error) console.error('[Error] 请求错误:', error)
const msg = messages.value[aiMsgIndex] const msg = messages.value[aiMsgIndex]
if (msg) { if (msg) {
msg.status = 'error' msg.status = 'error'
@@ -331,7 +342,7 @@ watch(
activeKey, activeKey,
() => { () => {
if (activeKey.value !== undefined) { if (activeKey.value !== undefined) {
console.log('🔄 [Conversation] 切换会话:', activeKey.value) console.log('[Conversation] 切换会话:', activeKey.value)
messages.value = [] messages.value = []
} }
}, },
@@ -370,18 +381,16 @@ const handleFileChange: AttachmentsProps['onChange'] = (info) =>
// ==================== Nodes ==================== // ==================== Nodes ====================
const placeholderNode = computed(() => const placeholderNode = computed(() =>
h(Space, { direction: 'vertical', size: 16, style: styles.value.placeholder }, () => [ h(Space, { direction: 'vertical', size: 16, style: styles.value.placeholder }, () => [
h(Welcome, { h(Welcome as Component, {
variant: 'borderless', variant: 'borderless',
icon: 'https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp', icon: 'https://mdn.alipayobjects.com/huamei_iwk9zp/afts/img/A*s5sNRo5LjfQAAAAAAAAAAAAADgCCAQ/fmt.webp',
title: "Hello, I'm Ant Design X",
description: '基于Ant DesignAGI产品界面解决方案创造更美好的智能愿景~', description: '基于Ant DesignAGI产品界面解决方案创造更美好的智能愿景~',
extra: h(Space, {}, () => [ extra: h(Space, {}, () => [
h(Button, { icon: h(ShareAltOutlined) }), h(Button, { icon: h(ShareAltOutlined) }),
h(Button, { icon: h(EllipsisOutlined) }), h(Button, { icon: h(EllipsisOutlined) }),
]), ]),
}), }, () => "Hello, I'm Ant Design X"),
h(Prompts, { h(Prompts as Component, {
title: '想要了是吧?',
items: placeholderPromptsItems, items: placeholderPromptsItems,
styles: { styles: {
list: { list: {
@@ -392,20 +401,20 @@ const placeholderNode = computed(() =>
}, },
}, },
onItemClick: onPromptsItemClick, onItemClick: onPromptsItemClick,
}), }, () => '想要了是吧?'),
]), ]),
) )
const items = computed<BubbleListProps['items']>(() => { const items = computed<BubbleListProps['items']>(() => {
console.log('🎨 [Items] 计算 items, messages数量:', messages.value.length) console.log('[Items] 计算 items, messages数量:', messages.value.length)
if (messages.value.length === 0) { if (messages.value.length === 0) {
console.log('🎨 [Items] 显示占位符') console.log('[Items] 显示占位符')
return [{ content: placeholderNode, variant: 'borderless' }] return [{ content: placeholderNode, variant: 'borderless' }]
} }
const result = messages.value.map(({ id, role, content, status }) => { const result = messages.value.map(({ id, role, content, status }) => {
console.log('🎨 [Items] 处理消息:', { id, role, status, contentLength: content?.length }) console.log('[Items] 处理消息:', { id, role, status, contentLength: content?.length })
return { return {
key: id, key: id,
loading: status === 'loading', loading: status === 'loading',
@@ -414,7 +423,7 @@ const items = computed<BubbleListProps['items']>(() => {
} }
}) })
console.log('🎨 [Items] 最终 items:', result) console.log('[Items] 最终 items:', result)
return result return result
}) })
</script> </script>
@@ -493,19 +502,19 @@ const items = computed<BubbleListProps['items']>(() => {
vertical vertical
gap="2" gap="2"
> >
<Typography.Text style="font-size: 30px; line-height: 1"> <div style="font-size: 30px; line-height: 1">
<CloudUploadOutlined /> <CloudUploadOutlined />
</Typography.Text> </div>
<Typography.Title :level="5" style="margin: 0; font-size: 14px; line-height: 1.5"> <div style="margin: 0; font-size: 14px; line-height: 1.5; font-weight: 600">
Upload files Upload files
</Typography.Title> </div>
<Typography.Text type="secondary"> <div style="color: rgba(0, 0, 0, 0.45)">
Click or drag files to this area to upload Click or drag files to this area to upload
</Typography.Text> </div>
</Flex> </Flex>
<Typography.Text v-if="type && type.type === 'drop'"> <div v-if="type && type.type === 'drop'">
Drop file here Drop file here
</Typography.Text> </div>
</template> </template>
</Attachments> </Attachments>
</Sender.Header> </Sender.Header>