UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

293 lines (253 loc) 7.06 kB
export interface ChatRequest { message: string; sessionId?: string; context?: Record<string, any>; } export interface ChatResponse { id: string; content: string; timestamp: Date; sessionId: string; sources?: Array<{ title: string; content: string; url?: string; score: number; }>; metadata?: Record<string, any>; } export interface StreamingChatResponse { id: string; content: string; isComplete: boolean; timestamp: Date; sessionId: string; sources?: Array<{ title: string; content: string; url?: string; score: number; }>; error?: string; } export class ChatbotAPIError extends Error { constructor(message: string, public status?: number, public code?: string) { super(message); this.name = "ChatbotAPIError"; } } export class ChatbotAPI { private baseUrl: string; private apiKey?: string; private sessionId: string; constructor(baseUrl: string = "/api/chatbot", apiKey?: string) { this.baseUrl = baseUrl; this.apiKey = apiKey; this.sessionId = this.generateSessionId(); } private generateSessionId(): string { return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } private async makeRequest<T>( endpoint: string, options: RequestInit = {} ): Promise<T> { const url = `${this.baseUrl}${endpoint}`; const headers: Record<string, string> = { "Content-Type": "application/json", ...(options.headers as Record<string, string>), }; if (this.apiKey) { headers["Authorization"] = `Bearer ${this.apiKey}`; } try { const response = await fetch(url, { ...options, headers, }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new ChatbotAPIError( errorData.message || `HTTP ${response.status}: ${response.statusText}`, response.status, errorData.code ); } return await response.json(); } catch (error) { if (error instanceof ChatbotAPIError) { throw error; } if (error instanceof TypeError && error.message.includes("fetch")) { throw new ChatbotAPIError( "네트워크 연결을 확인해주세요", 0, "NETWORK_ERROR" ); } throw new ChatbotAPIError( "API 요청 중 오류가 발생했습니다", 0, "UNKNOWN_ERROR" ); } } async sendMessage(request: ChatRequest): Promise<ChatResponse> { const response = await this.makeRequest<ChatResponse>("/chat", { method: "POST", body: JSON.stringify({ ...request, sessionId: request.sessionId || this.sessionId, }), }); return { ...response, timestamp: new Date(response.timestamp), }; } async *streamMessage( request: ChatRequest ): AsyncGenerator<StreamingChatResponse, void, unknown> { const url = `${this.baseUrl}/chat/stream`; const headers: Record<string, string> = { "Content-Type": "application/json", Accept: "text/event-stream", }; if (this.apiKey) { headers["Authorization"] = `Bearer ${this.apiKey}`; } try { const response = await fetch(url, { method: "POST", headers, body: JSON.stringify({ ...request, sessionId: request.sessionId || this.sessionId, }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({})); throw new ChatbotAPIError( errorData.message || `HTTP ${response.status}: ${response.statusText}`, response.status, errorData.code ); } const reader = response.body?.getReader(); if (!reader) { throw new ChatbotAPIError( "스트림을 읽을 수 없습니다", 0, "STREAM_ERROR" ); } const decoder = new TextDecoder(); let buffer = ""; try { while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split("\n"); buffer = lines.pop() || ""; for (const line of lines) { if (line.startsWith("data: ")) { const data = line.slice(6); if (data === "[DONE]") { return; } try { const parsed: StreamingChatResponse = JSON.parse(data); yield { ...parsed, timestamp: new Date(parsed.timestamp), }; } catch (parseError) { console.warn("Failed to parse streaming response:", parseError); } } } } } finally { reader.releaseLock(); } } catch (error) { if (error instanceof ChatbotAPIError) { throw error; } throw new ChatbotAPIError( "스트리밍 중 오류가 발생했습니다", 0, "STREAM_ERROR" ); } } async getSessionHistory(sessionId?: string): Promise<ChatResponse[]> { const id = sessionId || this.sessionId; const response = await this.makeRequest<{ messages: ChatResponse[] }>( `/sessions/${id}/history` ); return response.messages.map((msg) => ({ ...msg, timestamp: new Date(msg.timestamp), })); } async clearSession(sessionId?: string): Promise<void> { const id = sessionId || this.sessionId; await this.makeRequest(`/sessions/${id}`, { method: "DELETE", }); } getSessionId(): string { return this.sessionId; } setSessionId(sessionId: string): void { this.sessionId = sessionId; } // Health check for the API async healthCheck(): Promise<{ status: string; timestamp: Date }> { const response = await this.makeRequest<{ status: string; timestamp: string; }>("/health"); return { ...response, timestamp: new Date(response.timestamp), }; } } // Hook for using the chatbot API export function useChatbotAPI(baseUrl?: string, apiKey?: string) { const api = new ChatbotAPI(baseUrl, apiKey); return api; } // Retry mechanism for failed requests export async function withRetry<T>( operation: () => Promise<T>, maxRetries: number = 3, delay: number = 1000 ): Promise<T> { let lastError: Error; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; // Don't retry on client errors (4xx) if ( error instanceof ChatbotAPIError && error.status && error.status >= 400 && error.status < 500 ) { throw error; } if (attempt < maxRetries) { await new Promise((resolve) => setTimeout(resolve, delay * attempt)); } } } throw lastError!; }