/** * API 客户端 - 与 FastAPI 后端通信 * 使用 SuperJSON 处理序列化 */ import { serialize, deserialize } from './superjson'; import { mcpTokenManager } from './mcp-token-manager'; import catalog from './catalog/catalog'; // 配置 - 使用 Next.js rewrites 反向代理到 FastAPI const BACKEND_URL = '/api'; // 类型定义 export interface ChatMessage { role: 'user' | 'assistant'; content: string; } export interface ChatRequest { message: string; history: ChatMessage[]; } export interface ChatResponse { response: string; model: string; tool_calls: Array<{ tool: string; result: unknown; }>; has_tools: boolean; } export interface MCPServer { id: string; name: string; url: string; auth_type: 'none' | 'jwt'; enabled: boolean; } export interface MCPToolsResponse { tools: Array<{ name: string; description: string; input_schema: Record; }>; count: number; } export interface LoginRequest { email: string; password: string; } // 用户角色类型 export type UserRole = 'reader' | 'author' | 'admin'; // 用户信息接口 export interface UserInfo { id?: string; username: string; email?: string; role: UserRole; } export interface LoginResponse { success: boolean; session_id: string; username: string; server: string; role: UserRole; token?: string; } export interface RegisterRequest { email: string; username: string; password: string; } export interface AuthStatusResponse { authenticated: boolean; username?: string; server?: string; role?: string; } // API 客户端类 export class ApiClient { private baseUrl: string; private sessionId: string | null = null; private mcpTokens: Record = {}; private userInfo: UserInfo | null = null; constructor(baseUrl: string = BACKEND_URL) { this.baseUrl = baseUrl; } // 设置会话(同时持久化到 localStorage) setSession(sessionId: string) { this.sessionId = sessionId; // 持久化到 localStorage if (typeof window !== 'undefined') { localStorage.setItem('session_id', sessionId); } } // 设置用户信息 setUserInfo(userInfo: UserInfo) { this.userInfo = userInfo; // 持久化到 localStorage if (typeof window !== 'undefined') { localStorage.setItem('userInfo', JSON.stringify(userInfo)); } } // 获取用户信息 getUserInfo(): UserInfo | null { return this.userInfo; } // 根据角色获取 MCP URL getMcpUrl(): string { if (!this.userInfo) { return '/mcp/'; // 默认使用 User MCP } // admin 使用 Admin MCP,其他角色使用 User MCP return this.userInfo.role === 'admin' ? '/admin-mcp/' : '/mcp/'; } // 设置 MCP Tokens setMcpTokens(tokens: Record) { this.mcpTokens = { ...tokens }; } // 获取请求头 private getHeaders(): HeadersInit { const headers: HeadersInit = { 'Content-Type': 'application/json', }; // 携带所有已登录的 MCP Token const allMcpTokens = mcpTokenManager.getAllTokens(); if (Object.keys(allMcpTokens).length > 0) { headers['X-MCP-Tokens'] = JSON.stringify(allMcpTokens); } return headers; } // 通用请求方法 private async request( endpoint: string, options: RequestInit = {} ): Promise { const url = `${this.baseUrl}${endpoint}`; const response = await fetch(url, { ...options, headers: { ...this.getHeaders(), ...options.headers, }, }); if (!response.ok) { const error = await response.json().catch(() => ({ error: response.statusText })); throw new Error(error.error || error.detail || 'API request failed'); } return response.json(); } // 健康检查 async health(): Promise<{ status: string; model: string; mcp_servers: string[] }> { return this.request('/health'); } // 聊天(非流式) async chat(message: string, history: ChatMessage[] = []): Promise { // 构建请求体(包含组件定义) const requestBody = { message, history, // 发送可用组件列表给后端,实现动态组件注册 componentsPrompt: catalog.prompt({ mode: 'inline' }) }; return this.request('/chat', { method: 'POST', body: serialize(requestBody), }); } // 聊天(流式)- 返回 EventSource chatStream(message: string, history: ChatMessage[] = []): any { const url = new URL(`${this.baseUrl}/chat/stream`); const headers: Record = {}; if (this.sessionId) { headers['X-Session-ID'] = this.sessionId; } if (Object.keys(this.mcpTokens).length > 0) { headers['X-MCP-Tokens'] = JSON.stringify(this.mcpTokens); } // 构建请求体(包含组件定义) const body = { message, history, // 发送可用组件列表给后端,实现动态组件注册 componentsPrompt: catalog.prompt({ mode: 'inline' }) }; // 使用 fetch 创建流式连接 const eventSource = new EventSourcePolyfill(url.toString(), { headers, method: 'POST', body: JSON.stringify(body), }); return eventSource; } // 使用 fetch 的流式请求 async chatStreamFetch( message: string, history: ChatMessage[] = [], onEvent: (event: MessageEvent) => void, onError?: (error: Error) => void, onComplete?: () => void ): Promise<() => void> { const url = `${this.baseUrl}/chat/stream`; const controller = new AbortController(); try { // 构建请求体(包含组件定义和启用状态) const requestBody = { message, history, // 发送可用组件列表给后端,实现动态组件注册 componentsPrompt: catalog.prompt({ mode: 'inline' }), // 发送已启用的 MCP 列表,后端只获取这些 MCP 的工具 enabledMcpList: mcpTokenManager.getEnabledMcpList() }; const response = await fetch(url, { method: 'POST', headers: this.getHeaders(), body: JSON.stringify(requestBody), signal: controller.signal, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body?.getReader(); const decoder = new TextDecoder(); if (!reader) { throw new Error('Response body is null'); } let buffer = ''; let currentEvent = 'message'; // 默认事件类型 while (true) { const { done, value } = await reader.read(); if (done) { onComplete?.(); break; } buffer += decoder.decode(value, { stream: true }); // 处理 SSE 事件 const lines = buffer.split('\n'); buffer = lines.pop() || ''; for (const line of lines) { if (line.startsWith('event: ')) { // 提取事件类型 currentEvent = line.slice(7).trim(); } else if (line.startsWith('data: ')) { // 提取数据并使用当前事件类型 const data = line.slice(6); try { onEvent(new MessageEvent(currentEvent, { data })); } catch (e) { console.error('Error parsing SSE data:', e); } // 重置事件类型为默认值 currentEvent = 'message'; } } } } catch (error) { if (error instanceof Error && error.name !== 'AbortError') { onError?.(error); } } return () => controller.abort(); } // 获取 MCP 服务器列表 async listMcpServers(): Promise<{ servers: MCPServer[] }> { return this.request('/mcp/servers'); } // 获取 MCP 工具列表 async listMcpTools(): Promise { return this.request('/mcp/tools'); } // 用户登录 async login(email: string, password: string): Promise { const response = await this.request('/auth/login', { method: 'POST', body: JSON.stringify({ email, password }), }); if (response.session_id) { this.setSession(response.session_id); // 保存用户信息 this.setUserInfo({ username: response.username, role: response.role || 'reader', }); } return response; } // 管理员登录 async adminLogin(email: string, password: string): Promise { const response = await this.request('/auth/admin-login', { method: 'POST', body: JSON.stringify({ email, password }), }); if (response.session_id) { this.setSession(response.session_id); // 保存用户信息 this.setUserInfo({ username: response.username, role: response.role || 'admin', }); } return response; } // 用户注册 async register(email: string, username: string, password: string): Promise<{ success: boolean; message: string; user: unknown }> { return this.request('/auth/register', { method: 'POST', body: JSON.stringify({ email, username, password }), }); } // 登出(同时清除 localStorage) async logout(): Promise<{ success: boolean }> { const response = await this.request<{ success: boolean }>('/auth/logout', { method: 'POST', body: JSON.stringify({ session_id: this.sessionId }), }); this.sessionId = null; this.mcpTokens = {}; this.userInfo = null; // 清除 localStorage if (typeof window !== 'undefined') { localStorage.removeItem('session_id'); localStorage.removeItem('username'); localStorage.removeItem('userInfo'); } return response; } // 检查认证状态 async authStatus(): Promise { return this.request('/auth/status'); } } // EventSource polyfill for POST requests class EventSourcePolyfill { private url: string; private headers: Record; private onmessage: ((event: MessageEvent) => void) | null = null; private onerror: ((event: Event) => void) | null = null; private eventSource: EventSource | null = null; constructor(url: string, options: { headers: Record; method?: string; body?: string }) { this.url = url; this.headers = options.headers; // 简化实现 - 实际应用中需要更完整的 polyfill // 这里仅作为类型占位 } addEventListener(type: string, listener: (event: MessageEvent) => void) { if (type === 'message') { this.onmessage = listener; } } close() { this.eventSource?.close(); } } // 单例实例 export const apiClient = new ApiClient();