diff --git a/static/api.js b/static/js/api.js similarity index 97% rename from static/api.js rename to static/js/api.js index 8ca8a70..a730965 100644 --- a/static/api.js +++ b/static/js/api.js @@ -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);