chore:替换了ai-service.js
This commit is contained in:
@@ -62,53 +62,105 @@ export default {
|
||||
*/
|
||||
async sendStreamMessage(message, onChunk, onComplete) {
|
||||
// #ifdef MP-WEIXIN
|
||||
// 微信小程序:降级为普通请求 + 前端打字模拟
|
||||
try {
|
||||
const result = await this.sendMessage(message);
|
||||
const content = result.content || '';
|
||||
const conversationId = result.conversationId || '';
|
||||
return new Promise((resolve, reject) => {
|
||||
const socketTask = wx.connectSocket({
|
||||
url: 'wss://dev.aigc-quickapp.com/ws/aikefu',
|
||||
header: {}
|
||||
});
|
||||
|
||||
// 保存会话ID(确保连续对话)
|
||||
let content = '';
|
||||
let conversationId = '';
|
||||
let isAuthenticated = false;
|
||||
|
||||
socketTask.onOpen(() => {
|
||||
console.log('WebSocket 连接成功,开始认证...');
|
||||
socketTask.send({
|
||||
data: JSON.stringify({
|
||||
action: 'auth',
|
||||
uniacid: store.state.uniacid || '1',
|
||||
token: store.state.token || 'test_token',
|
||||
user_id: store.state.memberInfo?.id || 'anonymous'
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
socketTask.onMessage((res) => {
|
||||
try {
|
||||
const data = JSON.parse(res.data);
|
||||
console.log('收到 WebSocket 消息:', data);
|
||||
|
||||
if (data.type === 'auth_success') {
|
||||
console.log('认证成功,发送聊天消息...');
|
||||
isAuthenticated = true;
|
||||
socketTask.send({
|
||||
data: JSON.stringify({
|
||||
action: 'chat',
|
||||
uniacid: store.state.uniacid || '1',
|
||||
query: message,
|
||||
user_id: store.state.memberInfo?.id || 'anonymous',
|
||||
conversation_id: this.getConversationId() || ''
|
||||
})
|
||||
});
|
||||
} else if (data.type === 'auth_failed') {
|
||||
const errorMsg = '认证失败,请重新登录';
|
||||
console.error(errorMsg, data);
|
||||
reject(new Error(errorMsg));
|
||||
if (onComplete) onComplete({ error: errorMsg });
|
||||
socketTask.close();
|
||||
}
|
||||
|
||||
// 处理流式消息块
|
||||
else if (data.type === 'message' || data.event === 'message') {
|
||||
const text = data.answer || data.content || data.text || '';
|
||||
content += text;
|
||||
if (onChunk) onChunk(text);
|
||||
}
|
||||
|
||||
// 处理流结束
|
||||
else if (data.event === 'message_end' || data.type === 'message_end') {
|
||||
conversationId = data.conversation_id || '';
|
||||
if (conversationId) {
|
||||
this.setConversationId(conversationId);
|
||||
}
|
||||
|
||||
// 模拟打字效果
|
||||
let index = 0;
|
||||
const chunkSize = 2; // 每次显示2个字符
|
||||
return new Promise((resolve) => {
|
||||
const timer = setInterval(() => {
|
||||
if (index < content.length) {
|
||||
const chunk = content.substring(index, index + chunkSize);
|
||||
index += chunkSize;
|
||||
if (onChunk) onChunk(chunk);
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
if (onComplete) {
|
||||
onComplete({
|
||||
content: content,
|
||||
conversation_id: conversationId
|
||||
});
|
||||
onComplete({ content, conversation_id: conversationId });
|
||||
}
|
||||
resolve({ content, conversation_id: conversationId });
|
||||
socketTask.close();
|
||||
}
|
||||
}, 80); // 打字速度:80ms/次
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('小程序流式消息降级失败:', error);
|
||||
if (onComplete) {
|
||||
onComplete({ error: error.message || '发送失败' });
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
// H5:使用真实流式(EventSource / Fetch)
|
||||
return this.sendHttpStream(message, onChunk, onComplete);
|
||||
// 可选:处理 done
|
||||
else if (data.type === 'done') {
|
||||
console.log('对话完成:', data);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
console.error('WebSocket 消息解析失败:', e, '原始数据:', res.data);
|
||||
}
|
||||
});
|
||||
|
||||
socketTask.onError((err) => {
|
||||
const errorMsg = 'WebSocket 连接失败';
|
||||
console.error(errorMsg, err);
|
||||
reject(new Error(errorMsg));
|
||||
if (onComplete) onComplete({ error: errorMsg });
|
||||
});
|
||||
|
||||
socketTask.onClose(() => {
|
||||
console.log('WebSocket 连接已关闭');
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
if (!isAuthenticated || content === '') {
|
||||
console.warn('WebSocket 超时,强制关闭');
|
||||
socketTask.close();
|
||||
reject(new Error('AI服务响应超时'));
|
||||
if (onComplete) onComplete({ error: 'AI服务响应超时' });
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
* HTTP 流式请求(仅 H5 使用)
|
||||
*/
|
||||
@@ -132,11 +184,8 @@ export default {
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
if (!response.body) {
|
||||
throw new Error('响应体不可用');
|
||||
if (!response.ok || !response.body) {
|
||||
throw new Error('无效响应');
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
@@ -145,50 +194,64 @@ export default {
|
||||
let content = '';
|
||||
let conversationId = '';
|
||||
|
||||
function processBuffer(buf, callback) {
|
||||
const lines = buf.split('\n');
|
||||
buf = lines.pop() || '';
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
|
||||
// 按行分割,保留不完整的最后一行
|
||||
const lines = buffer.split('\n');
|
||||
buffer = lines.pop() || ''; // 未完成的行留到下次
|
||||
|
||||
for (const line of lines) {
|
||||
const trimmed = line.trim();
|
||||
if (trimmed.startsWith('data:')) {
|
||||
const jsonStr = trimmed.slice(5).trim();
|
||||
if (jsonStr) {
|
||||
try {
|
||||
const jsonStr = trimmed.slice(5).trim();
|
||||
if (jsonStr && jsonStr !== '[DONE]') {
|
||||
const data = JSON.parse(jsonStr);
|
||||
if (data.event === 'message') {
|
||||
const text = data.answer || data.text || '';
|
||||
content += text;
|
||||
callback(text);
|
||||
if (onChunk) onChunk(text);
|
||||
}
|
||||
if (data.conversation_id) {
|
||||
conversationId = data.conversation_id;
|
||||
}
|
||||
if (data.event === 'message_end') {
|
||||
// 可选:提前完成
|
||||
// 可提前结束
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('解析流数据失败:', e);
|
||||
console.warn('解析失败:', e, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
buffer += decoder.decode(value, { stream: true });
|
||||
buffer = processBuffer(buffer, (chunk) => {
|
||||
if (onChunk) onChunk(chunk);
|
||||
});
|
||||
// 处理最后残留的 buffer(如果有)
|
||||
if (buffer.trim().startsWith('data:')) {
|
||||
try {
|
||||
const jsonStr = buffer.trim().slice(5);
|
||||
if (jsonStr) {
|
||||
const data = JSON.parse(jsonStr);
|
||||
if (data.event === 'message') {
|
||||
const text = data.answer || '';
|
||||
content += text;
|
||||
if (onChunk) onChunk(text);
|
||||
}
|
||||
if (data.conversation_id) {
|
||||
conversationId = data.conversation_id;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('最后 buffer 解析失败:', e);
|
||||
}
|
||||
}
|
||||
|
||||
if (onComplete) {
|
||||
onComplete({
|
||||
content,
|
||||
conversation_id: conversationId
|
||||
});
|
||||
onComplete({ content, conversation_id: conversationId });
|
||||
}
|
||||
return { content, conversation_id: conversationId };
|
||||
} catch (error) {
|
||||
@@ -196,11 +259,6 @@ export default {
|
||||
throw error;
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
// 理论上不会执行到这里,但防止 fallback
|
||||
return this.sendStreamMessage(message, onChunk, onComplete);
|
||||
// #endif
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user