Compare commits

...

2 Commits

Author SHA1 Message Date
yvan bcc93fad2a wechat 2025-09-10 23:33:53 +08:00
yvan cb710b47df 1 2025-09-10 21:56:53 +08:00
5 changed files with 114 additions and 664 deletions

14
App.vue
View File

@ -42,6 +42,8 @@
// token
const token = uni.getStorageSync('token')
if (token) {
// tokenWebSocket
console.log('已有token跳过WebSocket初始化')
return
}
@ -74,7 +76,7 @@
if (result && result.token) {
//
this.saveLoginData(result)
await this.saveLoginData(result)
} else {
// code使
this.saveWxCode(loginRes.code)
@ -87,7 +89,7 @@
},
//
saveLoginData(result) {
async saveLoginData(result) {
uni.setStorageSync('token', result.token)
if (result.refreshToken) {
uni.setStorageSync('refreshToken', result.refreshToken)
@ -102,6 +104,9 @@
? 'profile_completed'
: 'phone_authed'
uni.setStorageSync('loginStep', loginStep)
// WebSocket
console.log('登录成功跳过WebSocket初始化')
},
// code
@ -109,6 +114,11 @@
uni.setStorageSync('wxLoginCode', code)
uni.setStorageSync('loginDate', new Date().toISOString())
uni.setStorageSync('loginStep', 'wx_logged')
},
// WebSocket
async initWebSocketConnection(token) {
console.log('WebSocket功能已移除使用HTTP API替代')
}
}
}

View File

@ -128,7 +128,7 @@ import {
formatChatMessage,
formatMessageTime
} from '@/http/api/assistant.js'
import { createPetAssistantSSE } from '@/utils/sse.js'
// WebSocket使HTTP API
export default {
data() {
@ -192,6 +192,9 @@ export default {
// AI
this.isThinking = true
// AI
let currentAiMessage = null
// DOM
this.$nextTick(() => {
this.forceScrollToBottom()
@ -205,11 +208,14 @@ export default {
})
try {
// 使SSE
// 使HTTP API
const response = await this.handleStreamResponse({
message: userInput,
temperature: 0.7,
maxTokens: 1000
}, (aiMessage) => {
// AIfallback
currentAiMessage = aiMessage
})
//
@ -235,17 +241,25 @@ export default {
})
if (fallbackResponse && fallbackResponse.data) {
const aiMessage = {
type: 'ai',
content: fallbackResponse.data.message || fallbackResponse.data.content || '抱歉,我现在无法回答您的问题。',
time: this.getCurrentTime(),
isSensitive: fallbackResponse.data.isSensitive || false,
tokenCount: fallbackResponse.data.tokenCount || 0,
responseTime: fallbackResponse.data.responseTime || 0
// AI
if (currentAiMessage) {
currentAiMessage.content = fallbackResponse.data.message || fallbackResponse.data.content || '抱歉,我现在无法回答您的问题。'
currentAiMessage.isSensitive = fallbackResponse.data.isSensitive || false
currentAiMessage.tokenCount = fallbackResponse.data.tokenCount || 0
currentAiMessage.responseTime = fallbackResponse.data.responseTime || 0
} else {
// AI
const aiMessage = {
type: 'ai',
content: fallbackResponse.data.message || fallbackResponse.data.content || '抱歉,我现在无法回答您的问题。',
time: this.getCurrentTime(),
isSensitive: fallbackResponse.data.isSensitive || false,
tokenCount: fallbackResponse.data.tokenCount || 0,
responseTime: fallbackResponse.data.responseTime || 0
}
this.messageList.push(aiMessage)
}
//
if (fallbackResponse.data.isSensitive) {
uni.showToast({
@ -255,14 +269,13 @@ export default {
})
}
this.messageList.push(aiMessage)
this.forceScrollToBottom()
} else {
this.handleApiError('API响应格式异常')
this.handleApiError('API响应格式异常', currentAiMessage)
}
} catch (fallbackError) {
console.error('普通请求也失败:', fallbackError)
this.handleApiError(fallbackError.message || '网络请求失败')
this.handleApiError(fallbackError.message || '网络请求失败', currentAiMessage)
}
} finally {
//
@ -273,17 +286,26 @@ export default {
/**
* 处理API错误
* @param {string} errorMessage 错误信息
* @param {Object} existingAiMessage 已存在的AI消息对象可选
*/
handleApiError(errorMessage) {
const errorMessage_display = {
type: 'ai',
content: `抱歉,我遇到了一些问题:${errorMessage}\n\n请稍后再试或者检查网络连接。`,
time: this.getCurrentTime(),
isError: true
handleApiError(errorMessage, existingAiMessage = null) {
const errorContent = `抱歉,我遇到了一些问题:${errorMessage}\n\n请稍后再试或者检查网络连接。`
if (existingAiMessage) {
// AI
existingAiMessage.content = errorContent
existingAiMessage.isError = true
} else {
//
const errorMessage_display = {
type: 'ai',
content: errorContent,
time: this.getCurrentTime(),
isError: true
}
this.messageList.push(errorMessage_display)
}
//
this.messageList.push(errorMessage_display)
this.forceScrollToBottom()
},
@ -291,28 +313,43 @@ export default {
* 加载聊天历史记录
*/
async loadChatHistory() {
console.log('开始加载聊天历史记录...')
try {
const response = await getAssistantHistory({
page: 1,
pageSize: 50
pageSize: 20
})
if (response && response.data && response.data.list && response.data.list.length > 0) {
console.log('历史记录API响应:', response)
if (response && Array.isArray(response) && response.length > 0) {
//
const historyMessages = response.data.list.map(msg => formatChatMessage(msg))
const historyMessages = response.map(msg => formatChatMessage(msg))
console.log('格式化后的历史消息:', historyMessages)
this.messageList = historyMessages
//
this.$nextTick(() => {
this.scrollToBottom()
})
console.log('历史记录加载成功,共', historyMessages.length, '条消息')
} else {
//
console.log('没有历史记录或数据格式异常:', response)
this.messageList = []
}
} catch (error) {
console.error('加载历史记录失败:', error)
//
//
uni.showToast({
title: '加载历史记录失败',
icon: 'none',
duration: 2000
})
//
this.messageList = []
}
},
@ -360,11 +397,12 @@ export default {
/**
* 处理SSE流式响应
* 处理HTTP流式响应
* @param {Object} messageData 消息数据
* @param {Function} onAiMessageCreated 创建AI消息后的回调函数
* @returns {Promise} 返回流式响应处理结果
*/
async handleStreamResponse(messageData) {
async handleStreamResponse(messageData, onAiMessageCreated) {
// AI
const aiMessage = {
type: 'ai',
@ -378,29 +416,46 @@ export default {
//
this.messageList.push(aiMessage)
// 使SSE
return createPetAssistantSSE(messageData, {
onChunk: (chunk) => {
//
aiMessage.content = chunk.totalContent
this.forceScrollToBottom()
},
onComplete: (result) => {
// AI
if (onAiMessageCreated) {
onAiMessageCreated(aiMessage)
}
// 使HTTP API
return streamAskPetAssistant(messageData)
.then(result => {
console.log('HTTP流式响应完成:', result)
//
aiMessage.content = result.message || result.content || ''
//
if (result.data.isSensitive !== undefined) {
aiMessage.isSensitive = result.data.isSensitive
if (result.isSensitive !== undefined) {
aiMessage.isSensitive = result.isSensitive
}
if (result.data.tokenCount !== undefined) {
aiMessage.tokenCount = result.data.tokenCount
if (result.tokenCount !== undefined) {
aiMessage.tokenCount = result.tokenCount
}
if (result.data.responseTime !== undefined) {
aiMessage.responseTime = result.data.responseTime
if (result.responseTime !== undefined) {
aiMessage.responseTime = result.responseTime
}
},
onError: (error) => {
console.error('SSE流式响应失败:', error)
}
})
//
this.$forceUpdate()
return result
})
.catch(error => {
console.error('HTTP流式响应失败:', error)
//
uni.showToast({
title: '消息发送失败',
icon: 'error',
duration: 2000
})
//
aiMessage.content = '抱歉,消息发送失败,请重试。'
aiMessage.isError = true
//
this.$forceUpdate()
throw error
})
},
scrollToBottom() {

View File

@ -1,141 +0,0 @@
/**
* SSE工具类测试文件
* 用于验证SSE功能是否正常工作
*/
import { createSSERequest, createPetAssistantSSE, SSE_STATUS } from './sse.js'
/**
* 测试基础SSE功能
*/
export function testBasicSSE() {
console.log('开始测试基础SSE功能...')
const testData = {
message: '测试消息',
temperature: 0.7,
maxTokens: 100
}
let receivedChunks = 0
let totalContent = ''
return createSSERequest({
url: `${uni.$u.http.config.baseURL}/pet/user/assistant/stream-ask`,
data: testData,
onChunk: (chunk) => {
receivedChunks++
totalContent = chunk.totalContent
console.log(`接收到第${receivedChunks}个数据块:`, chunk.content)
},
onComplete: (result) => {
console.log('SSE响应完成:', {
totalChunks: receivedChunks,
totalContent: totalContent,
metadata: result.data
})
},
onError: (error) => {
console.error('SSE测试失败:', error)
}
})
}
/**
* 测试宠物助手专用SSE
*/
export function testPetAssistantSSE() {
console.log('开始测试宠物助手SSE功能...')
const testData = {
message: '你好,请介绍一下自己',
temperature: 0.7,
maxTokens: 200
}
let chunkCount = 0
return createPetAssistantSSE(testData, {
onChunk: (chunk) => {
chunkCount++
console.log(`宠物助手SSE - 第${chunkCount}块:`, chunk.content)
},
onComplete: (result) => {
console.log('宠物助手SSE完成:', {
chunks: chunkCount,
message: result.data.message,
isSensitive: result.data.isSensitive,
tokenCount: result.data.tokenCount
})
},
onError: (error) => {
console.error('宠物助手SSE失败:', error)
}
})
}
/**
* 运行所有测试
*/
export async function runAllTests() {
console.log('=== 开始SSE工具类测试 ===')
try {
// 测试基础SSE
console.log('\n1. 测试基础SSE功能')
await testBasicSSE()
// 等待一秒
await new Promise(resolve => setTimeout(resolve, 1000))
// 测试宠物助手SSE
console.log('\n2. 测试宠物助手SSE功能')
await testPetAssistantSSE()
console.log('\n=== SSE工具类测试完成 ===')
return true
} catch (error) {
console.error('SSE测试过程中发生错误:', error)
return false
}
}
/**
* 简单的功能验证
*/
export function validateSSEFunctions() {
console.log('验证SSE工具类函数是否存在...')
const checks = [
{ name: 'createSSERequest', func: createSSERequest },
{ name: 'createPetAssistantSSE', func: createPetAssistantSSE },
{ name: 'SSE_STATUS', obj: SSE_STATUS }
]
let allValid = true
checks.forEach(check => {
if (typeof check.func === 'function' || typeof check.obj === 'object') {
console.log(`${check.name} - 正常`)
} else {
console.error(`${check.name} - 缺失或类型错误`)
allValid = false
}
})
if (allValid) {
console.log('✅ 所有SSE工具类函数验证通过')
} else {
console.error('❌ SSE工具类函数验证失败')
}
return allValid
}
export default {
testBasicSSE,
testPetAssistantSSE,
runAllTests,
validateSSEFunctions
}

View File

@ -1,216 +0,0 @@
# SSE工具类使用指南
## 概述
SSE (Server-Sent Events) 工具类提供了通用的流式响应处理功能,支持实时数据流处理、回调机制和错误处理。
## 主要功能
### 1. 基础SSE请求 - `createSSERequest()`
最灵活的底层API支持完全自定义配置。
```javascript
import { createSSERequest } from '@/utils/sse.js'
const response = await createSSERequest({
url: 'https://api.example.com/stream',
method: 'POST',
data: { message: '你好' },
headers: { 'Custom-Header': 'value' },
onChunk: (chunk) => {
console.log('接收到数据:', chunk.content)
console.log('累积内容:', chunk.totalContent)
},
onComplete: (result) => {
console.log('流式响应完成:', result.data)
},
onError: (error) => {
console.error('发生错误:', error)
},
doneMarker: '[DONE]' // 自定义结束标记
})
```
### 2. 默认配置SSE请求 - `createDefaultSSERequest()`
带有默认配置的便捷API。
```javascript
import { createDefaultSSERequest } from '@/utils/sse.js'
const response = await createDefaultSSERequest(
'/api/chat/stream', // API端点
{ message: '你好' }, // 请求数据
{
onChunk: (chunk) => {
// 处理数据块
updateUI(chunk.content)
},
onComplete: (result) => {
// 处理完成
console.log('完成:', result.data.message)
}
}
)
```
### 3. 宠物助手专用SSE - `createPetAssistantSSE()`
专门为宠物助手功能设计的API。
```javascript
import { createPetAssistantSSE } from '@/utils/sse.js'
const response = await createPetAssistantSSE(
{
message: '我的宠物生病了怎么办?',
temperature: 0.7,
maxTokens: 1000
},
{
onChunk: (chunk) => {
// 实时更新AI回复
aiMessage.content = chunk.totalContent
scrollToBottom()
},
onComplete: (result) => {
// 更新元数据
aiMessage.isSensitive = result.data.isSensitive
aiMessage.tokenCount = result.data.tokenCount
}
}
)
```
### 4. 可取消的SSE请求 - `createCancellableSSE()`
支持取消操作的SSE请求。
```javascript
import { createCancellableSSE, SSE_STATUS } from '@/utils/sse.js'
const { promise, cancel, getStatus } = createCancellableSSE({
url: '/api/stream',
data: { message: '长时间处理的请求' },
onChunk: (chunk) => {
console.log('状态:', getStatus()) // 'streaming'
updateContent(chunk.content)
}
})
// 5秒后取消请求
setTimeout(() => {
if (getStatus() === SSE_STATUS.STREAMING) {
cancel()
console.log('请求已取消')
}
}, 5000)
try {
const result = await promise
console.log('请求完成:', result)
} catch (error) {
console.log('请求被取消或失败:', error)
}
```
## 回调函数参数
### onChunk(chunk)
- `chunk.content` - 当前数据块内容
- `chunk.totalContent` - 累积的完整内容
- `chunk.chunk` - 原始解析的数据对象
### onComplete(result)
- `result.data.message` - 完整的响应消息
- `result.data.isSensitive` - 是否包含敏感内容
- `result.data.tokenCount` - 使用的token数量
- `result.data.responseTime` - 响应时间
### onError(error)
- 网络错误、解析错误或其他异常
## 状态枚举
```javascript
import { SSE_STATUS } from '@/utils/sse.js'
SSE_STATUS.PENDING // 等待中
SSE_STATUS.STREAMING // 流式传输中
SSE_STATUS.COMPLETED // 已完成
SSE_STATUS.ERROR // 发生错误
SSE_STATUS.CANCELLED // 已取消
```
## 在Vue组件中使用
```javascript
// 在组件中导入
import { createPetAssistantSSE } from '@/utils/sse.js'
export default {
methods: {
async sendMessage() {
// 创建消息对象
const aiMessage = {
type: 'ai',
content: '',
time: this.getCurrentTime()
}
this.messageList.push(aiMessage)
// 发起SSE请求
try {
const result = await createPetAssistantSSE(
{ message: this.inputMessage },
{
onChunk: (chunk) => {
// 实时更新内容
aiMessage.content = chunk.totalContent
this.$nextTick(() => {
this.scrollToBottom()
})
},
onComplete: (result) => {
// 更新元数据
Object.assign(aiMessage, result.data)
},
onError: (error) => {
console.error('SSE失败:', error)
// 可以在这里实现降级处理
}
}
)
} catch (error) {
// 处理异常
this.handleError(error)
}
}
}
}
```
## 注意事项
1. **自动认证**: 工具类会自动添加Authorization头部
2. **错误处理**: 建议同时实现onError回调和try-catch
3. **内存管理**: 长时间的流式响应注意内存使用
4. **网络状态**: 考虑网络中断的情况
5. **降级处理**: SSE失败时可以降级到普通API请求
## 扩展使用
可以基于基础API创建更多专用函数
```javascript
// 创建客服聊天SSE
export function createCustomerServiceSSE(data, callbacks) {
return createDefaultSSERequest('/api/customer/stream', data, callbacks)
}
// 创建代码生成SSE
export function createCodeGenerationSSE(data, callbacks) {
return createDefaultSSERequest('/api/code/generate', data, callbacks)
}
```

View File

@ -1,258 +0,0 @@
/**
* SSE (Server-Sent Events) 流式响应工具类
* 提供通用的SSE数据流处理功能
*/
/**
* 创建SSE流式请求
* @param {Object} options 配置选项
* @param {string} options.url 请求URL
* @param {string} options.method 请求方法默认POST
* @param {Object} options.data 请求数据
* @param {Object} options.headers 请求头会自动添加SSE相关头部
* @param {Function} options.onChunk 接收到数据块时的回调 (chunk) => {}
* @param {Function} options.onComplete 流式响应完成时的回调 (result) => {}
* @param {Function} options.onError 发生错误时的回调 (error) => {}
* @param {string} options.doneMarker 结束标记默认为 '[DONE]'
* @returns {Promise} 返回Promiseresolve时返回完整结果
*/
export function createSSERequest(options) {
const {
url,
method = 'POST',
data = {},
headers = {},
onChunk,
onComplete,
onError,
doneMarker = '[DONE]'
} = options
return new Promise((resolve, reject) => {
// 构建完整的请求头
const requestHeaders = {
'Content-Type': 'application/json',
'Accept': 'text/event-stream',
'Cache-Control': 'no-cache',
'Authorization': `Bearer ${uni.getStorageSync('token') || ''}`,
...headers
}
// 累积的响应数据
let accumulatedData = {
content: '',
metadata: {}
}
// 发起uni.request请求
const requestTask = uni.request({
url,
method,
data,
header: requestHeaders,
responseType: 'text',
success: (res) => {
try {
// 处理SSE数据流
const lines = res.data.split('\n')
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = line.substring(6).trim()
// 检查是否为结束标记
if (data === doneMarker) {
// 流式响应结束
const result = {
data: {
message: accumulatedData.content,
...accumulatedData.metadata
}
}
// 调用完成回调
if (onComplete) {
onComplete(result)
}
resolve(result)
return
}
try {
const parsed = JSON.parse(data)
// 处理内容数据
if (parsed.content) {
accumulatedData.content += parsed.content
// 调用数据块回调
if (onChunk) {
onChunk({
content: parsed.content,
totalContent: accumulatedData.content,
chunk: parsed
})
}
}
// 更新元数据
if (parsed.isSensitive !== undefined) {
accumulatedData.metadata.isSensitive = parsed.isSensitive
}
if (parsed.tokenCount !== undefined) {
accumulatedData.metadata.tokenCount = parsed.tokenCount
}
if (parsed.responseTime !== undefined) {
accumulatedData.metadata.responseTime = parsed.responseTime
}
// 保存其他元数据
Object.keys(parsed).forEach(key => {
if (!['content', 'isSensitive', 'tokenCount', 'responseTime'].includes(key)) {
accumulatedData.metadata[key] = parsed[key]
}
})
} catch (parseError) {
console.warn('解析SSE数据失败:', parseError, data)
// 继续处理其他数据,不中断流程
}
}
}
// 如果没有收到结束标记,也认为完成
const result = {
data: {
message: accumulatedData.content,
...accumulatedData.metadata
}
}
if (onComplete) {
onComplete(result)
}
resolve(result)
} catch (error) {
console.error('处理SSE响应失败:', error)
if (onError) {
onError(error)
}
reject(error)
}
},
fail: (error) => {
console.error('SSE请求失败:', error)
if (onError) {
onError(error)
}
reject(error)
}
})
// 返回请求任务,允许外部取消请求
return requestTask
})
}
/**
* 创建带有默认配置的SSE请求
* @param {string} endpoint API端点路径
* @param {Object} data 请求数据
* @param {Object} callbacks 回调函数 {onChunk, onComplete, onError}
* @returns {Promise} SSE请求Promise
*/
export function createDefaultSSERequest(endpoint, data, callbacks = {}) {
const baseURL = uni.$u?.http?.config?.baseURL || ''
return createSSERequest({
url: `${baseURL}${endpoint}`,
method: 'POST',
data,
...callbacks
})
}
/**
* 专用于宠物助手的SSE请求
* @param {Object} data 请求数据
* @param {Object} callbacks 回调函数
* @returns {Promise} SSE请求Promise
*/
export function createPetAssistantSSE(data, callbacks = {}) {
return createDefaultSSERequest('/pet/user/assistant/stream-ask', data, callbacks)
}
/**
* SSE请求状态枚举
*/
export const SSE_STATUS = {
PENDING: 'pending',
STREAMING: 'streaming',
COMPLETED: 'completed',
ERROR: 'error',
CANCELLED: 'cancelled'
}
/**
* 创建可取消的SSE请求
* @param {Object} options 配置选项
* @returns {Object} 返回包含promise和cancel方法的对象
*/
export function createCancellableSSE(options) {
let requestTask = null
let status = SSE_STATUS.PENDING
const promise = new Promise((resolve, reject) => {
const enhancedOptions = {
...options,
onChunk: (chunk) => {
status = SSE_STATUS.STREAMING
if (options.onChunk) {
options.onChunk(chunk)
}
},
onComplete: (result) => {
status = SSE_STATUS.COMPLETED
if (options.onComplete) {
options.onComplete(result)
}
resolve(result)
},
onError: (error) => {
status = SSE_STATUS.ERROR
if (options.onError) {
options.onError(error)
}
reject(error)
}
}
requestTask = createSSERequest(enhancedOptions)
})
return {
promise,
cancel: () => {
if (requestTask && status === SSE_STATUS.STREAMING) {
requestTask.abort?.()
status = SSE_STATUS.CANCELLED
}
},
getStatus: () => status
}
}
export default {
createSSERequest,
createDefaultSSERequest,
createPetAssistantSSE,
createCancellableSSE,
SSE_STATUS
}