691 lines
25 KiB
JavaScript
691 lines
25 KiB
JavaScript
|
|
/**
|
|||
|
|
* 聊天API服务 - 对接新的 oneapi_replace 后端API
|
|||
|
|
* 使用 /retrieve 接口进行问答
|
|||
|
|
*/
|
|||
|
|
class ChatService {
|
|||
|
|
constructor() {
|
|||
|
|
// 使用本地开发服务地址
|
|||
|
|
// 生产环境: 'http://101.200.154.78:8081'
|
|||
|
|
// 本地开发: 'http://localhost:8000'
|
|||
|
|
this.baseURL = 'http://localhost:8000';
|
|||
|
|
// 生成会话ID
|
|||
|
|
this.sessionId = this.generateSessionId();
|
|||
|
|
// 存储会话历史(本地管理)
|
|||
|
|
this.conversations = new Map();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成会话ID
|
|||
|
|
*/
|
|||
|
|
generateSessionId() {
|
|||
|
|
// 使用UUID格式
|
|||
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|||
|
|
var r = Math.random() * 16 | 0;
|
|||
|
|
var v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|||
|
|
return v.toString(16);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送API请求的通用方法
|
|||
|
|
*/
|
|||
|
|
async request(endpoint, options = {}) {
|
|||
|
|
// 保留token处理,用于其他需要认证的服务
|
|||
|
|
const token = window.TokenManager ? window.TokenManager.getToken() : null;
|
|||
|
|
|
|||
|
|
const defaultOptions = {
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
// 新后端不需要Authorization header,但保留以备其他服务使用
|
|||
|
|
// 'Authorization': token ? `Bearer ${token}` : ''
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const config = {
|
|||
|
|
...defaultOptions,
|
|||
|
|
...options,
|
|||
|
|
headers: {
|
|||
|
|
...defaultOptions.headers,
|
|||
|
|
...options.headers
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const response = await fetch(`${this.baseURL}${endpoint}`, config);
|
|||
|
|
|
|||
|
|
// 处理空响应(如204 No Content)
|
|||
|
|
if (response.status === 204) {
|
|||
|
|
return { success: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const data = await response.json();
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
// 保留401错误处理,用于其他需要认证的服务
|
|||
|
|
if (response.status === 401 && window.TokenManager) {
|
|||
|
|
window.TokenManager.clearAll();
|
|||
|
|
window.location.href = '/auth/pages/login.html';
|
|||
|
|
throw new Error('认证失败,请重新登录');
|
|||
|
|
}
|
|||
|
|
throw new Error(data.message || data.detail || `请求失败: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return data;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Chat API request error:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送消息(同步方式)
|
|||
|
|
* @param {object} messageData - 消息数据
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async sendMessage(messageData) {
|
|||
|
|
try {
|
|||
|
|
// 转换为新后端的请求格式
|
|||
|
|
const isDeepResearch = messageData.mode === 'research' || messageData.deepResearch;
|
|||
|
|
|
|||
|
|
// 生产环境使用 /query 接口,参数更简单
|
|||
|
|
const response = await this.request('/query', {
|
|||
|
|
method: 'POST',
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
query: messageData.message,
|
|||
|
|
mode: isDeepResearch ? 'complex' : '0', // 自动判断或复杂模式
|
|||
|
|
save_output: false // 不保存到文件
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 保存到本地会话历史
|
|||
|
|
const conversationId = messageData.conversationId || this.sessionId;
|
|||
|
|
if (!this.conversations.has(conversationId)) {
|
|||
|
|
this.conversations.set(conversationId, []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const userMessage = {
|
|||
|
|
role: 'user',
|
|||
|
|
content: messageData.message,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const assistantMessage = {
|
|||
|
|
role: 'assistant',
|
|||
|
|
content: response.answer,
|
|||
|
|
timestamp: new Date().toISOString(),
|
|||
|
|
metadata: response.metadata
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.conversations.get(conversationId).push(userMessage, assistantMessage);
|
|||
|
|
|
|||
|
|
// 转换响应格式以适配前端
|
|||
|
|
return {
|
|||
|
|
code: 200,
|
|||
|
|
data: {
|
|||
|
|
conversationId: conversationId,
|
|||
|
|
messageId: Date.now().toString(),
|
|||
|
|
content: response.answer,
|
|||
|
|
reference: response.supporting_facts,
|
|||
|
|
events: response.supporting_events,
|
|||
|
|
metadata: response.metadata
|
|||
|
|
},
|
|||
|
|
message: '成功'
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Send message failed:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 发送消息(真正的流式方式)
|
|||
|
|
* @param {object} messageData - 消息数据
|
|||
|
|
* @param {function} onMessage - 消息回调函数
|
|||
|
|
* @param {function} onComplete - 完成回调函数
|
|||
|
|
* @param {function} onError - 错误回调函数
|
|||
|
|
* @param {function} onThinking - 思考过程回调函数
|
|||
|
|
* @param {function} onStatus - 状态更新回调函数(新增)
|
|||
|
|
* @returns {AbortController} 可用于取消请求的控制器
|
|||
|
|
*/
|
|||
|
|
sendMessageStream(messageData, onMessage, onComplete, onError, onThinking = null, onStatus = null) {
|
|||
|
|
const abortController = new AbortController();
|
|||
|
|
|
|||
|
|
// 保存原始的abort方法
|
|||
|
|
const originalAbort = abortController.abort.bind(abortController);
|
|||
|
|
|
|||
|
|
// 启动异步流处理(但立即返回AbortController)
|
|||
|
|
this._startStream(abortController, originalAbort, messageData, onMessage, onComplete, onError, onThinking, onStatus);
|
|||
|
|
|
|||
|
|
// 立即返回AbortController,不等待异步处理
|
|||
|
|
return abortController;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
async _startStream(abortController, originalAbort, messageData, onMessage, onComplete, onError, onThinking, onStatus) {
|
|||
|
|
// 检查是否有流式端点
|
|||
|
|
const streamEndpoint = '/retrieve/stream';
|
|||
|
|
const hasStreamSupport = await this.checkStreamSupport();
|
|||
|
|
|
|||
|
|
if (!hasStreamSupport) {
|
|||
|
|
// 降级到原有的伪流式实现
|
|||
|
|
return this.sendMessageStreamLegacy(messageData, onMessage, onComplete, onError, onThinking);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const isDeepResearch = messageData.deepResearch || false;
|
|||
|
|
|
|||
|
|
// 构建请求body
|
|||
|
|
const requestBody = {
|
|||
|
|
query: messageData.message,
|
|||
|
|
mode: isDeepResearch ? 'complex' : '0',
|
|||
|
|
use_cache: true,
|
|||
|
|
save_output: false
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 使用fetch处理SSE
|
|||
|
|
const response = await fetch(`${this.baseURL}${streamEndpoint}`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json',
|
|||
|
|
'Accept': 'text/event-stream'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify(requestBody),
|
|||
|
|
signal: abortController.signal
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 读取流式响应
|
|||
|
|
const reader = response.body.getReader();
|
|||
|
|
const decoder = new TextDecoder();
|
|||
|
|
let buffer = '';
|
|||
|
|
let accumulatedAnswer = '';
|
|||
|
|
let supportingFacts = [];
|
|||
|
|
let supportingEvents = [];
|
|||
|
|
let metadata = {};
|
|||
|
|
let taskId = null; // 任务ID
|
|||
|
|
|
|||
|
|
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) {
|
|||
|
|
if (line.startsWith('data: ')) {
|
|||
|
|
const data = line.slice(6);
|
|||
|
|
|
|||
|
|
if (data === '[DONE]') {
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const event = JSON.parse(data);
|
|||
|
|
|
|||
|
|
switch (event.type) {
|
|||
|
|
case 'task_created':
|
|||
|
|
// 保存任务ID
|
|||
|
|
taskId = event.task_id;
|
|||
|
|
console.log('[ChatService] Task created:', taskId);
|
|||
|
|
|
|||
|
|
// 重写abort方法,添加取消任务的逻辑
|
|||
|
|
abortController.abort = () => {
|
|||
|
|
// 发送取消请求到后端
|
|||
|
|
if (taskId) {
|
|||
|
|
fetch(`${this.baseURL}/task/cancel/${taskId}`, {
|
|||
|
|
method: 'POST'
|
|||
|
|
}).catch(err => {
|
|||
|
|
console.log('[ChatService] Cancel request failed:', err);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
// 执行原始的abort
|
|||
|
|
originalAbort();
|
|||
|
|
};
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'cache_hit':
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'cache',
|
|||
|
|
message: event.message,
|
|||
|
|
cached: true
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'cache_miss':
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'cache',
|
|||
|
|
message: event.message,
|
|||
|
|
cached: false
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'starting':
|
|||
|
|
case 'complexity_check':
|
|||
|
|
case 'documents':
|
|||
|
|
case 'sufficiency_check':
|
|||
|
|
case 'sub_queries':
|
|||
|
|
case 'iteration':
|
|||
|
|
case 'generating':
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus(event);
|
|||
|
|
}
|
|||
|
|
if (onThinking && event.message) {
|
|||
|
|
onThinking(event.message + '\n');
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'answer_chunk':
|
|||
|
|
const chunk = event.data?.text || '';
|
|||
|
|
accumulatedAnswer += chunk;
|
|||
|
|
if (onMessage) {
|
|||
|
|
onMessage(chunk);
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'supporting_info':
|
|||
|
|
supportingFacts = event.data?.supporting_facts || [];
|
|||
|
|
supportingEvents = event.data?.supporting_events || [];
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'answer': // rag_api_server_production.py 使用这个类型
|
|||
|
|
// 处理最终答案
|
|||
|
|
if (event.data?.content) {
|
|||
|
|
accumulatedAnswer = event.data.content;
|
|||
|
|
}
|
|||
|
|
supportingFacts = event.data?.supporting_facts || supportingFacts;
|
|||
|
|
supportingEvents = event.data?.supporting_events || supportingEvents;
|
|||
|
|
metadata = event.data || {};
|
|||
|
|
// 触发完成状态
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'complete',
|
|||
|
|
message: event.message || '✅ 答案生成完成'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'complete':
|
|||
|
|
const finalAnswer = event.data?.answer || accumulatedAnswer;
|
|||
|
|
metadata = event.data || {};
|
|||
|
|
// 触发完成状态
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'complete',
|
|||
|
|
message: '✅ 答案生成完成'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'cached':
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'cached',
|
|||
|
|
message: event.message
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'cancelled':
|
|||
|
|
// 任务被取消
|
|||
|
|
if (onStatus) {
|
|||
|
|
onStatus({
|
|||
|
|
type: 'cancelled',
|
|||
|
|
message: event.message || '任务已被取消'
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
if (onError) {
|
|||
|
|
onError(new Error(event.message || '任务已被取消'));
|
|||
|
|
}
|
|||
|
|
return abortController; // 提前返回
|
|||
|
|
break;
|
|||
|
|
|
|||
|
|
case 'error':
|
|||
|
|
throw new Error(event.message || '检索过程出错');
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.warn('Failed to parse SSE event:', e);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存到会话历史
|
|||
|
|
const conversationId = messageData.conversationId || this.sessionId;
|
|||
|
|
if (!this.conversations.has(conversationId)) {
|
|||
|
|
this.conversations.set(conversationId, []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const userMessage = {
|
|||
|
|
role: 'user',
|
|||
|
|
content: messageData.message,
|
|||
|
|
timestamp: new Date().toISOString()
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const assistantMessage = {
|
|||
|
|
role: 'assistant',
|
|||
|
|
content: accumulatedAnswer,
|
|||
|
|
timestamp: new Date().toISOString(),
|
|||
|
|
metadata: metadata,
|
|||
|
|
supporting_facts: supportingFacts,
|
|||
|
|
supporting_events: supportingEvents
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
this.conversations.get(conversationId).push(userMessage, assistantMessage);
|
|||
|
|
|
|||
|
|
// 完成回调
|
|||
|
|
const result = {
|
|||
|
|
conversationId: conversationId,
|
|||
|
|
messageId: Date.now().toString(),
|
|||
|
|
content: accumulatedAnswer,
|
|||
|
|
reference: supportingFacts,
|
|||
|
|
events: supportingEvents,
|
|||
|
|
metadata: metadata
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
if (onComplete) {
|
|||
|
|
onComplete(result);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error.name === 'AbortError') {
|
|||
|
|
console.log('Stream aborted');
|
|||
|
|
} else {
|
|||
|
|
console.error('Stream error:', error);
|
|||
|
|
onError(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 原有的伪流式实现(作为降级方案)
|
|||
|
|
*/
|
|||
|
|
async sendMessageStreamLegacy(messageData, onMessage, onComplete, onError, onThinking = null) {
|
|||
|
|
const abortController = new AbortController();
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
const isDeepResearch = messageData.deepResearch || false;
|
|||
|
|
const showThinking = messageData.showThinking || false;
|
|||
|
|
|
|||
|
|
if (showThinking && onThinking) {
|
|||
|
|
onThinking('正在分析问题复杂度...\n');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const response = await this.request('/retrieve', {
|
|||
|
|
method: 'POST',
|
|||
|
|
signal: abortController.signal,
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
query: messageData.message,
|
|||
|
|
mode: isDeepResearch ? 'complex' : '0',
|
|||
|
|
save_output: false
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 显示思考过程
|
|||
|
|
if (showThinking && onThinking && response.metadata) {
|
|||
|
|
const metadata = response.metadata;
|
|||
|
|
let thinkingText = '';
|
|||
|
|
|
|||
|
|
if (metadata.complexity_level) {
|
|||
|
|
thinkingText += `**问题复杂度**: ${metadata.complexity_level}\n`;
|
|||
|
|
thinkingText += `**置信度**: ${(metadata.complexity_confidence * 100).toFixed(1)}%\n`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (thinkingText) {
|
|||
|
|
onThinking(thinkingText);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 保存到会话历史
|
|||
|
|
const conversationId = messageData.conversationId || this.sessionId;
|
|||
|
|
if (!this.conversations.has(conversationId)) {
|
|||
|
|
this.conversations.set(conversationId, []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
this.conversations.get(conversationId).push(
|
|||
|
|
{ role: 'user', content: messageData.message, timestamp: new Date().toISOString() },
|
|||
|
|
{ role: 'assistant', content: response.answer, timestamp: new Date().toISOString() }
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 模拟流式输出
|
|||
|
|
const chunks = this.splitIntoChunks(response.answer, 20);
|
|||
|
|
for (const chunk of chunks) {
|
|||
|
|
if (abortController.signal.aborted) break;
|
|||
|
|
onMessage(chunk);
|
|||
|
|
await new Promise(resolve => setTimeout(resolve, 30));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
onComplete({
|
|||
|
|
conversationId: conversationId,
|
|||
|
|
messageId: Date.now().toString(),
|
|||
|
|
content: response.answer,
|
|||
|
|
reference: response.supporting_facts,
|
|||
|
|
events: response.supporting_events,
|
|||
|
|
metadata: response.metadata
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
} catch (error) {
|
|||
|
|
if (error.name !== 'AbortError') {
|
|||
|
|
console.error('Legacy stream error:', error);
|
|||
|
|
onError(error);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return abortController;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查是否支持流式端点
|
|||
|
|
*/
|
|||
|
|
async checkStreamSupport() {
|
|||
|
|
try {
|
|||
|
|
// 直接返回true,因为我们知道端点存在
|
|||
|
|
// 避免OPTIONS请求导致的405错误
|
|||
|
|
return true;
|
|||
|
|
} catch (error) {
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 将文本分割成小块以模拟流式输出
|
|||
|
|
*/
|
|||
|
|
splitIntoChunks(text, chunkSize = 20) {
|
|||
|
|
const chunks = [];
|
|||
|
|
for (let i = 0; i < text.length; i += chunkSize) {
|
|||
|
|
chunks.push(text.slice(i, i + chunkSize));
|
|||
|
|
}
|
|||
|
|
return chunks;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取用户的会话列表
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async getConversations() {
|
|||
|
|
// 从localStorage和内存中获取会话列表
|
|||
|
|
try {
|
|||
|
|
const conversations = [];
|
|||
|
|
const chatManager = window.chatManager;
|
|||
|
|
|
|||
|
|
// 从chatManager获取
|
|||
|
|
if (chatManager && chatManager.chats) {
|
|||
|
|
for (const [id, chat] of chatManager.chats) {
|
|||
|
|
conversations.push({
|
|||
|
|
id: id,
|
|||
|
|
title: chat.title || '新对话',
|
|||
|
|
createdAt: chat.createdAt || new Date().toISOString(),
|
|||
|
|
updatedAt: chat.updatedAt || new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 也包含当前服务中的会话
|
|||
|
|
for (const [id, messages] of this.conversations) {
|
|||
|
|
if (!conversations.find(c => c.id === id)) {
|
|||
|
|
const firstMessage = messages.find(m => m.role === 'user');
|
|||
|
|
conversations.push({
|
|||
|
|
id: id,
|
|||
|
|
title: firstMessage ? firstMessage.content.substring(0, 30) + '...' : '新对话',
|
|||
|
|
createdAt: messages[0]?.timestamp || new Date().toISOString(),
|
|||
|
|
updatedAt: messages[messages.length - 1]?.timestamp || new Date().toISOString()
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
conversations: conversations
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Get conversations failed:', error);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
conversations: []
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取会话的消息历史
|
|||
|
|
* @param {string|number} conversationId - 会话ID
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async getConversationMessages(conversationId) {
|
|||
|
|
// 从localStorage和内存中获取消息历史
|
|||
|
|
try {
|
|||
|
|
// 首先尝试从内存中获取
|
|||
|
|
if (this.conversations.has(conversationId)) {
|
|||
|
|
const messages = this.conversations.get(conversationId);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
messages: messages.map((msg, index) => ({
|
|||
|
|
id: msg.id || index.toString(),
|
|||
|
|
role: msg.role,
|
|||
|
|
content: msg.content,
|
|||
|
|
timestamp: msg.timestamp
|
|||
|
|
}))
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 然后尝试从chatManager获取
|
|||
|
|
const chatManager = window.chatManager;
|
|||
|
|
|
|||
|
|
if (chatManager && chatManager.chats && chatManager.chats.has(conversationId)) {
|
|||
|
|
const chat = chatManager.chats.get(conversationId);
|
|||
|
|
const messages = chat.messages || [];
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
messages: messages.map((msg, index) => ({
|
|||
|
|
id: msg.id || index.toString(),
|
|||
|
|
role: msg.role,
|
|||
|
|
content: msg.content,
|
|||
|
|
timestamp: msg.timestamp
|
|||
|
|
}))
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
messages: []
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Get conversation messages failed:', error);
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
messages: []
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建新会话(通过发送第一条消息)
|
|||
|
|
* @param {string} message - 第一条消息
|
|||
|
|
* @param {string} mode - 聊天模式
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async createConversation(message, mode = 'chat') {
|
|||
|
|
// 生成新的会话ID
|
|||
|
|
const newSessionId = this.generateSessionId();
|
|||
|
|
|
|||
|
|
// 使用新会话ID发送消息
|
|||
|
|
return this.sendMessage({
|
|||
|
|
message: message,
|
|||
|
|
conversationId: newSessionId,
|
|||
|
|
mode: mode
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除会话
|
|||
|
|
* @param {string|number} conversationId - 会话ID
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async deleteConversation(conversationId) {
|
|||
|
|
// 从内存中删除
|
|||
|
|
this.conversations.delete(conversationId);
|
|||
|
|
|
|||
|
|
// 如果chatManager存在,也从中删除
|
|||
|
|
if (window.chatManager && window.chatManager.chats) {
|
|||
|
|
window.chatManager.chats.delete(conversationId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: '会话已删除'
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查服务状态
|
|||
|
|
* @returns {Promise<boolean>} 服务是否可用
|
|||
|
|
*/
|
|||
|
|
async checkServiceStatus() {
|
|||
|
|
try {
|
|||
|
|
// 新后端使用 /health 接口进行健康检查
|
|||
|
|
const response = await fetch(`${this.baseURL}/health`);
|
|||
|
|
return response.ok;
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Service check failed:', error);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清除会话历史
|
|||
|
|
* @param {string} sessionId - 会话ID
|
|||
|
|
* @returns {Promise} API响应
|
|||
|
|
*/
|
|||
|
|
async clearConversation(sessionId) {
|
|||
|
|
try {
|
|||
|
|
// 清除本地会话历史
|
|||
|
|
if (this.conversations.has(sessionId)) {
|
|||
|
|
this.conversations.set(sessionId, []);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
message: '会话已清除'
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('Clear conversation failed:', error);
|
|||
|
|
throw error;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 创建并导出服务实例
|
|||
|
|
try {
|
|||
|
|
window.ChatAPIService = new ChatService();
|
|||
|
|
console.log('ChatAPIService 创建成功 (新后端):', window.ChatAPIService);
|
|||
|
|
console.log('后端地址:', window.ChatAPIService.baseURL);
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('ChatAPIService 创建失败:', error);
|
|||
|
|
}
|