| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412 |
- /**
- * API 客户端 - 与 FastAPI 后端通信
- * 使用 SuperJSON 处理序列化
- */
- import { serialize, deserialize } from './superjson';
- import { mcpTokenManager } from './mcp-token-manager';
- import { generateComponentsPrompt } from './component-registry';
- // 配置 - 使用 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<string, unknown>;
- }>;
- 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<string, string> = {};
- 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<string, string>) {
- 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<T>(
- endpoint: string,
- options: RequestInit = {}
- ): Promise<T> {
- 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<ChatResponse> {
- // 构建请求体(包含组件定义)
- const requestBody = {
- message,
- history,
- // 发送可用组件列表给后端,实现动态组件注册
- availableComponents: generateComponentsPrompt()
- };
- return this.request<ChatResponse>('/chat', {
- method: 'POST',
- body: serialize(requestBody),
- });
- }
- // 聊天(流式)- 返回 EventSource
- chatStream(message: string, history: ChatMessage[] = []): any {
- const url = new URL(`${this.baseUrl}/chat/stream`);
- const headers: Record<string, string> = {};
- 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,
- // 发送可用组件列表给后端,实现动态组件注册
- availableComponents: generateComponentsPrompt()
- };
- // 使用 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,
- // 发送可用组件列表给后端,实现动态组件注册
- availableComponents: generateComponentsPrompt(),
- // 发送已启用的 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<MCPToolsResponse> {
- return this.request('/mcp/tools');
- }
- // 用户登录
- async login(email: string, password: string): Promise<LoginResponse> {
- const response = await this.request<LoginResponse>('/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<LoginResponse> {
- const response = await this.request<LoginResponse>('/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<AuthStatusResponse> {
- return this.request('/auth/status');
- }
- }
- // EventSource polyfill for POST requests
- class EventSourcePolyfill {
- private url: string;
- private headers: Record<string, string>;
- 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<string, string>; 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();
|