更新 static/js/api.js

This commit is contained in:
2025-06-06 16:49:13 +08:00
parent e9c3b8cac8
commit d7c238de03

View File

@ -1,265 +1,265 @@
// static/js/api.js
/*
* 通用的API请求函数
* @param { string } endpoint API端点不包含 / api前缀
* @param { string } method HTTP方法'GET', 'POST', 'DELETE'
* @param { Object } [data = null] 请求体数据GET请求时转换为查询参数
* @param { string } [baseUrl = ''] 可选的基础URL如果提供则覆盖默认的API_BASE_URL
* @returns { Promise < Object >} API响应数据
*/
export async function apiRequest(endpoint, method = 'GET', data = null, baseUrl = '') {
let url;
if (baseUrl && (baseUrl.startsWith('http://') || baseUrl.startsWith('https://'))) {
// 如果baseUrl是完整的URL则直接拼接endpoint
url = `${baseUrl}${endpoint}`;
} else {
// 否则,使用默认的 /api 前缀或提供的相对baseUrl
url = `${baseUrl || '/api'}${endpoint}`;
}
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
},
};
if (method === 'GET' && data) {
const query = new URLSearchParams(data).toString();
url = `${url}?${query}`;
} else if (data) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.error || errorData.message || `API请求失败: ${response.status}`);
}
// 根据Content-Type判断返回JSON还是文本
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
// 假设是纯文本,例如日志文件
return await response.text();
}
} catch (error) {
console.error(`API请求 (${method} ${url}) 失败:`, error);
throw error; // 重新抛出错误以便调用方处理
}
}
// --- 配置管理 API ---
export async function fetchConfig() {
return apiRequest('/config', 'GET');
}
export async function saveConfig(configData) {
return apiRequest('/config', 'POST', configData);
}
// --- 角色管理 API ---
export async function fetchRoles() {
return apiRequest('/roles', 'GET');
}
export async function createRole(roleId, roleName) {
return apiRequest('/roles', 'POST', { id: roleId, name: roleName });
}
export async function deleteRole(roleId) {
return apiRequest(`/roles/${roleId}`, 'DELETE');
}
// --- 记忆管理 API ---
export async function fetchMemories() {
return apiRequest('/memories', 'GET');
}
export async function createMemory(memoryId, memoryName) {
return apiRequest('/memories', 'POST', { id: memoryId, name: memoryName });
}
export async function deleteMemory(memoryId) {
return apiRequest(`/memories/${memoryId}`, 'DELETE');
}
// --- 会话管理 API ---
export async function fetchActiveSession() {
return apiRequest('/active_session', 'GET');
}
export async function setActiveSession(roleId, memoryId) {
return apiRequest('/active_session', 'POST', { role_id: roleId, memory_id: memoryId });
}
// --- 特征内容 API ---
export async function fetchFeaturesContent() {
return apiRequest('/features_content', 'GET');
}
export async function saveFeaturesContent(content) {
return apiRequest('/features_content', 'POST', content);
}
// --- 记忆内容 API ---
export async function fetchMemoryContent() {
return apiRequest('/memory_content', 'GET');
}
export async function saveMemoryContent(content) {
return apiRequest('/memory_content', 'POST', content);
}
export async function triggerMemoryUpdate() {
return apiRequest('/memory/trigger_update', 'POST');
}
// --- 聊天与日志 API ---
export async function sendMessage(message, useStream = false) {
if (!useStream) {
// 使用标准响应方式
return apiRequest('/chat', 'POST', { message: message });
} else {
// 使用流式响应方式
const url = '/api/chat?stream=true';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message }),
};
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.error || errorData.message || `API请求失败: ${response.status}`);
}
// 检查是否返回了流
if (response.body) {
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
// 创建一个更强健的处理SSE的异步迭代器
return {
[Symbol.asyncIterator]() {
let buffer = '';
return {
async next() {
try {
// 读取新数据块
const { done, value } = await reader.read();
if (done) {
console.log('流已结束');
// 处理buffer中剩余的数据
if (buffer.trim().length > 0) {
console.log('处理buffer中剩余数据:', buffer);
const finalValue = buffer;
buffer = '';
return { done: false, value: finalValue };
}
return { done: true, value: undefined };
}
// 解码二进制数据并添加到缓冲区
buffer += decoder.decode(value, { stream: true });
// 检查是否有完整的SSE消息 (以"data: "开头的行)
// 注意SSE消息格式为 "data: {...}\n\n"
const lines = buffer.split('\n\n');
// 如果没有完整的消息,继续读取
if (lines.length < 2) {
return this.next();
}
// 提取完整的消息并更新buffer
const completeMessage = lines[0];
buffer = lines.slice(1).join('\n\n');
// 移除 "data: " 前缀并解析 JSON
if (completeMessage.startsWith('data: ')) {
try {
const jsonString = completeMessage.substring(6); // 移除 "data: "
const parsedData = JSON.parse(jsonString);
if (parsedData.end) {
// 如果收到结束标记,则流结束
console.log('收到流结束标记。');
return { done: true, value: undefined };
} else if (parsedData.chunk !== undefined) {
// 返回 chunk 内容
console.log('解析并返回 chunk:', parsedData.chunk.substring(0, 50) + '...');
return { done: false, value: parsedData.chunk };
}
} catch (parseError) {
console.error('解析SSE数据失败:', parseError, '原始数据:', completeMessage);
// 如果解析失败,可以返回原始数据抛出错误,这里选择返回原始数据
return { done: false, value: completeMessage };
}
}
// 如果不是有效的SSE数据行继续读取
return this.next();
} catch (error) {
console.error('读取流时出错:', error);
return { done: true, value: undefined };
}
}
};
}
};
} else {
throw new Error('服务器未返回流响应');
}
} catch (error) {
console.error(`流式API请求 (POST ${url}) 失败:`, error);
throw error;
}
}
}
export async function fetchChatLog(limit = null) {
return apiRequest('/chat_log', 'GET', limit ? { limit: limit } : null);
}
export async function clearChatLog() {
return apiRequest('/chat_log', 'DELETE');
}
// --- 模型列表 API ---
export async function fetchModels() {
try {
// 始终通过后端代理获取模型列表
const data = await apiRequest('/proxy_models', 'GET');
if (data && Array.isArray(data.models)) {
return data.models.map(model => {
if (typeof model === 'string') {
return model;
} else if (model && typeof model.name === 'string') {
return model.name;
}
return null;
}).filter(model => model !== null);
}
return [];
} catch (error) {
console.error("获取模型列表失败:", error);
throw error;
}
}
// --- 日志 API ---
export async function fetchLogs() {
return apiRequest('/logs', 'GET');
}
console.log("api.js loaded. Type of fetchConfig:", typeof fetchConfig);
// static/js/api.js
/*
* 通用的API请求函数
* @param { string } endpoint API端点不包含 / api前缀
* @param { string } method HTTP方法'GET', 'POST', 'DELETE'
* @param { Object } [data = null] 请求体数据GET请求时转换为查询参数
* @param { string } [baseUrl = ''] 可选的基础URL如果提供则覆盖默认的API_BASE_URL
* @returns { Promise < Object >} API响应数据
*/
export async function apiRequest(endpoint, method = 'GET', data = null, baseUrl = '') {
let url;
if (baseUrl && (baseUrl.startsWith('http://') || baseUrl.startsWith('https://'))) {
// 如果baseUrl是完整的URL则直接拼接endpoint
url = `${baseUrl}${endpoint}`;
} else {
// 否则,使用默认的 /api 前缀或提供的相对baseUrl
url = `${baseUrl || '/api'}${endpoint}`;
}
const options = {
method: method,
headers: {
'Content-Type': 'application/json',
},
};
if (method === 'GET' && data) {
const query = new URLSearchParams(data).toString();
url = `${url}?${query}`;
} else if (data) {
options.body = JSON.stringify(data);
}
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.error || errorData.message || `API请求失败: ${response.status}`);
}
// 根据Content-Type判断返回JSON还是文本
const contentType = response.headers.get('Content-Type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
// 假设是纯文本,例如日志文件
return await response.text();
}
} catch (error) {
console.error(`API请求 (${method} ${url}) 失败:`, error);
throw error; // 重新抛出错误以便调用方处理
}
}
// --- 配置管理 API ---
export async function fetchConfig() {
return apiRequest('/config', 'GET');
}
export async function saveConfig(configData) {
return apiRequest('/config', 'POST', configData);
}
// --- 角色管理 API ---
export async function fetchRoles() {
return apiRequest('/roles', 'GET');
}
export async function createRole(roleId, roleName) {
return apiRequest('/roles', 'POST', { id: roleId, name: roleName });
}
export async function deleteRole(roleId) {
return apiRequest(`/roles/${roleId}`, 'DELETE');
}
// --- 记忆管理 API ---
export async function fetchMemories() {
return apiRequest('/memories', 'GET');
}
export async function createMemory(memoryId, memoryName) {
return apiRequest('/memories', 'POST', { id: memoryId, name: memoryName });
}
export async function deleteMemory(memoryId) {
return apiRequest(`/memories/${memoryId}`, 'DELETE');
}
// --- 会话管理 API ---
export async function fetchActiveSession() {
return apiRequest('/active_session', 'GET');
}
export async function setActiveSession(roleId, memoryId) {
return apiRequest('/active_session', 'POST', { role_id: roleId, memory_id: memoryId });
}
// --- 特征内容 API ---
export async function fetchFeaturesContent() {
return apiRequest('/features_content', 'GET');
}
export async function saveFeaturesContent(content) {
return apiRequest('/features_content', 'POST', content);
}
// --- 记忆内容 API ---
export async function fetchMemoryContent() {
return apiRequest('/memory_content', 'GET');
}
export async function saveMemoryContent(content) {
return apiRequest('/memory_content', 'POST', content);
}
export async function triggerMemoryUpdate() {
return apiRequest('/memory/trigger_update', 'POST');
}
// --- 聊天与日志 API ---
export async function sendMessage(message, useStream = false) {
if (!useStream) {
// 使用标准响应方式
return apiRequest('/chat', 'POST', { message: message });
} else {
// 使用流式响应方式
const url = '/api/chat?stream=true';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: message }),
};
try {
const response = await fetch(url, options);
if (!response.ok) {
const errorData = await response.json().catch(() => ({ message: response.statusText }));
throw new Error(errorData.error || errorData.message || `API请求失败: ${response.status}`);
}
// 检查是否返回了流
if (response.body) {
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
// 创建一个更强健的处理SSE的异步迭代器
return {
[Symbol.asyncIterator]() {
let buffer = '';
return {
async next() {
try {
// 读取新数据块
const { done, value } = await reader.read();
if (done) {
console.log('流已结束');
// 处理buffer中剩余的数据
if (buffer.trim().length > 0) {
console.log('处理buffer中剩余数据:', buffer);
const finalValue = buffer;
buffer = '';
return { done: false, value: finalValue };
}
return { done: true, value: undefined };
}
// 解码二进制数据并添加到缓冲区
buffer += decoder.decode(value, { stream: true });
// 检查是否有完整的SSE消息 (以"data: "开头的行)
// 注意SSE消息格式为 "data: {...}\n\n"
const lines = buffer.split('\n\n');
// 如果没有完整的消息,继续读取
if (lines.length < 2) {
return this.next();
}
// 提取完整的消息并更新buffer
const completeMessage = lines[0];
buffer = lines.slice(1).join('\n\n');
// 移除 "data: " 前缀并解析 JSON
if (completeMessage.startsWith('data: ')) {
try {
const jsonString = completeMessage.substring(6); // 移除 "data: "
const parsedData = JSON.parse(jsonString);
if (parsedData.end) {
// 如果收到结束标记,则流结束
console.log('收到流结束标记。');
return { done: true, value: undefined };
} else if (parsedData.chunk !== undefined) {
// 返回 chunk 内容
console.log('解析并返回 chunk:', parsedData.chunk.substring(0, 50) + '...');
return { done: false, value: parsedData.chunk };
}
} catch (parseError) {
console.error('解析SSE数据失败:', parseError, '原始数据:', completeMessage);
// 如果解析失败,可以返回原始数据抛出错误,这里选择返回原始数据
return { done: false, value: completeMessage };
}
}
// 如果不是有效的SSE数据行继续读取
return this.next();
} catch (error) {
console.error('读取流时出错:', error);
return { done: true, value: undefined };
}
}
};
}
};
} else {
throw new Error('服务器未返回流响应');
}
} catch (error) {
console.error(`流式API请求 (POST ${url}) 失败:`, error);
throw error;
}
}
}
export async function fetchChatLog(limit = null) {
return apiRequest('/chat_log', 'GET', limit ? { limit: limit } : null);
}
export async function clearChatLog() {
return apiRequest('/chat_log', 'DELETE');
}
// --- 模型列表 API ---
export async function fetchModels() {
try {
// 始终通过后端代理获取模型列表
const data = await apiRequest('/proxy_models', 'GET');
if (data && Array.isArray(data.models)) {
return data.models.map(model => {
if (typeof model === 'string') {
return model;
} else if (model && typeof model.name === 'string') {
return model.name;
}
return null;
}).filter(model => model !== null);
}
return [];
} catch (error) {
console.error("获取模型列表失败:", error);
throw error;
}
}
// --- 日志 API ---
export async function fetchLogs() {
return apiRequest('/logs', 'GET');
}
console.log("api.js loaded. Type of fetchConfig:", typeof fetchConfig);