Files
gemini_boy/static/js/api.js

266 lines
10 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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);