first commit

This commit is contained in:
闫旭隆
2025-10-17 09:31:28 +08:00
commit 4698145045
589 changed files with 196795 additions and 0 deletions

View File

@ -0,0 +1,395 @@
/**
* 认证服务API
*/
class AuthService {
// 本地开发: 'http://localhost:8080/api'
// 生产环境: 'http://101.200.154.78:8080/api'
static BASE_URL = 'http://localhost:8080/api';
/**
* 发送验证码
* @param {string} account - 手机号或邮箱
* @param {string} type - 验证码类型login/register
* @returns {Promise} API响应
*/
static async sendVerifyCode(account, type = 'login') {
try {
const response = await fetch(`${this.BASE_URL}/auth/send-code`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
contact: account // 后端使用contact字段
})
});
const data = await response.json();
// 后端使用code字段判断成功与否200表示成功
if (data.code !== 200) {
throw new Error(data.message || '发送验证码失败');
}
return {
success: true,
data: data.data,
message: data.message || '验证码发送成功'
};
} catch (error) {
console.error('Send verify code failed:', error);
return {
success: false,
message: error.message || '网络错误,请稍后重试'
};
}
}
/**
* 用户登录
* @param {string} account - 手机号或邮箱
* @param {string} verifyCode - 验证码
* @returns {Promise} API响应
*/
static async login(account, verifyCode) {
try {
const response = await fetch(`${this.BASE_URL}/auth/login`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
contact: account, // 后端使用contact字段
code: verifyCode // 后端使用code字段
})
});
const data = await response.json();
// 后端使用code字段判断成功与否200表示成功
if (data.code !== 200) {
throw new Error(data.message || '登录失败');
}
// 后端响应的数据在data字段中
const authData = data.data;
if (authData && authData.token) {
TokenManager.saveToken(authData.token, true);
// 构建user对象
const user = {
id: authData.id,
username: authData.username,
phone: authData.phone,
email: authData.email
};
TokenManager.saveUser(user, true);
}
return {
success: true,
data: authData,
message: data.message || '登录成功'
};
} catch (error) {
console.error('Login failed:', error);
return {
success: false,
message: error.message || '网络错误,请稍后重试'
};
}
}
/**
* 用户注册
* @param {string} username - 用户名
* @param {string} account - 手机号或邮箱
* @param {string} verifyCode - 验证码
* @returns {Promise} API响应
*/
static async register(username, account, verifyCode) {
try {
const response = await fetch(`${this.BASE_URL}/auth/register`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
username,
// 判断是邮箱还是手机号
...(account.includes('@') ? { email: account } : { phone: account }),
code: verifyCode // 后端使用code字段
})
});
const data = await response.json();
// 后端使用code字段判断成功与否200表示成功
if (data.code !== 200) {
throw new Error(data.message || '注册失败');
}
// 后端响应的数据在data字段中
const authData = data.data;
if (authData && authData.token) {
TokenManager.saveToken(authData.token, true);
// 构建user对象
const user = {
id: authData.id,
username: authData.username,
phone: authData.phone,
email: authData.email
};
TokenManager.saveUser(user, true);
}
return {
success: true,
data: authData,
message: data.message || '注册成功'
};
} catch (error) {
console.error('Register failed:', error);
return {
success: false,
message: error.message || '网络错误,请稍后重试'
};
}
}
/**
* 用户登出
* @returns {Promise} API响应
*/
static async logout() {
try {
const authHeader = TokenManager.getAuthHeader();
const response = await fetch(`${this.BASE_URL}/auth/logout`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeader
}
});
// 无论服务器响应如何,都清除本地存储
TokenManager.clearAll();
if (!response.ok) {
console.warn('Server logout failed, but local data cleared');
}
return {
success: true,
message: '退出登录成功'
};
} catch (error) {
console.error('Logout failed:', error);
// 即使请求失败,也要清除本地数据
TokenManager.clearAll();
return {
success: true,
message: '已退出登录'
};
}
}
/**
* 获取用户信息
* @returns {Promise} API响应
*/
static async getUserInfo() {
try {
const authHeader = TokenManager.getAuthHeader();
if (!authHeader) {
throw new Error('未登录');
}
// 从本地存储获取用户ID
const localUser = TokenManager.getUser();
if (!localUser || !localUser.id) {
throw new Error('用户ID不存在');
}
// 使用 /api/users/{id} 接口替代 /api/users/me
const response = await fetch(`${this.BASE_URL}/users/${localUser.id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
...authHeader
}
});
// 特殊处理403错误
if (response.status === 403) {
throw new Error('403:权限配置问题');
}
// 特殊处理404错误
if (response.status === 404) {
throw new Error('404:接口未实现');
}
// 处理401未授权
if (response.status === 401) {
TokenManager.clearAll();
throw new Error('登录已过期,请重新登录');
}
// 尝试解析响应
let data;
try {
const text = await response.text();
if (!text) {
throw new Error('响应为空');
}
data = JSON.parse(text);
} catch (parseError) {
throw new Error(`解析响应失败: ${response.status}`);
}
// UserController直接返回User对象不是包装的ResponseResult
// 所以这里data就是用户信息
const userData = data;
// 确保不暴露密码等敏感信息
if (userData) {
delete userData.password;
delete userData.salt;
// 更新本地存储的用户信息
TokenManager.saveUser(userData, true);
}
return {
success: true,
data: userData,
message: data.message || '获取成功'
};
} catch (error) {
console.error('Get user info failed:', error);
return {
success: false,
message: error.message || '网络错误,请稍后重试'
};
}
}
/**
* 刷新Token
* @returns {Promise} API响应
*/
static async refreshToken() {
try {
const refreshToken = TokenManager.getRefreshToken();
if (!refreshToken) {
throw new Error('无刷新令牌');
}
const response = await fetch(`${this.BASE_URL}/auth/refresh`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
refreshToken
})
});
const data = await response.json();
if (!response.ok) {
// 刷新失败,清除所有本地数据
TokenManager.clearAll();
throw new Error(data.error || data.message || 'Token刷新失败');
}
// Spring Boot直接返回token数据
if (data.token) {
TokenManager.saveToken(data.token, true);
if (data.refreshToken) {
TokenManager.saveRefreshToken(data.refreshToken);
}
}
return {
success: true,
data: data,
message: 'Token刷新成功'
};
} catch (error) {
console.error('Refresh token failed:', error);
return {
success: false,
message: error.message || '刷新失败,请重新登录'
};
}
}
/**
* 检查认证状态
* @returns {Promise} 认证状态
*/
static async checkAuthStatus() {
try {
// 先检查本地token
const isTokenValid = TokenManager.isTokenValid();
const localUser = TokenManager.getUser();
if (!isTokenValid) {
console.log('本地token无效用户未登录');
return {
isAuthenticated: false,
user: null,
message: '未登录'
};
}
// 如果有token尝试从服务器获取最新用户信息
try {
const userResult = await this.getUserInfo();
if (userResult.success) {
console.log('成功从服务器获取用户信息:', userResult.data);
return {
isAuthenticated: true,
user: userResult.data,
message: '已登录'
};
}
} catch (userError) {
console.warn('获取用户信息失败:', userError);
// 如果API调用失败但有本地数据使用本地数据作为降级方案
if (localUser) {
console.log('API调用失败使用本地缓存的用户信息:', localUser);
return {
isAuthenticated: true,
user: localUser,
message: '已登录(使用缓存)'
};
}
}
return {
isAuthenticated: false,
user: null,
message: '未登录'
};
} catch (error) {
console.error('Check auth status failed:', error);
return {
isAuthenticated: false,
user: null,
message: '认证检查失败'
};
}
}
}
// 导出供其他模块使用
if (typeof module !== 'undefined' && module.exports) {
module.exports = AuthService;
}

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3

View File

@ -0,0 +1,691 @@
/**
* 聊天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);
}

View File

@ -0,0 +1,2 @@
[ZoneTransfer]
ZoneId=3