import { Injectable, Logger, } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; interface AgentApiRequest { conversation_id: string; query: string; sender_id: string; } interface AgentApiResponse { reply?: string; message?: string; content?: string; response?: string; text?: string; answer?: string; } @Injectable() export class AgentService { private readonly logger = new Logger(AgentService.name); constructor( private readonly configService: ConfigService, ) {} // ==================== Mention Bot Logic ==================== /** * Check if message contains a mention of the configured bot username */ containsMention(content: string): boolean { const botUsername = this.configService.get('AGENT_BOT_USERNAME') || this.configService.get('agent.botUsername') || 'studybot'; const pattern = new RegExp(`@${botUsername}\\b`, 'i'); return pattern.test(content); } /** * Extract the prompt from a message by removing the bot mention */ extractPrompt(content: string): string { const botUsername = this.configService.get('AGENT_BOT_USERNAME') || this.configService.get('agent.botUsername') || 'studybot'; const pattern = new RegExp(`@${botUsername}\\b`, 'gi'); return content.replace(pattern, '').trim(); } /** * Get bot user ID from config */ getBotUserId(): string | undefined { const botUserId = this.configService.get('AGENT_BOT_USER_ID'); if (botUserId) return botUserId; return this.configService.get('agent.botUserId') || undefined; } /** * Call the external agent API and return the reply text * Returns null if agent is not configured or returns empty */ async callAgent( roomId: string, userId: string, prompt: string, username?: string, ): Promise { const baseUrl = this.configService.get('AGENT_API_URL') || this.configService.get('agent.apiUrl'); if (!baseUrl) { this.logger.warn('AGENT_API_URL not configured, skipping agent call'); return 'Xin lỗi, tôi đang gặp sự cố kết nối. Vui lòng thử lại sau!'; } if (!prompt) { this.logger.debug('Empty prompt, skipping agent call'); return null; } try { this.logger.log(`Calling agent API for room ${roomId}, user ${userId}`); const reply = await this.callAgentApi({ conversation_id: roomId, query: prompt, sender_id: username || userId, }); if (!reply || reply.trim().length === 0) { this.logger.warn('Agent returned empty reply'); return 'Xin lỗi, tôi không hiểu câu hỏi của bạn. Vui lòng thử lại!'; } this.logger.log(`Agent replied for room ${roomId}`); return reply; } catch (error) { this.logger.error(`Failed to call agent: ${error.message}`); return 'Xin lỗi, tôi đang gặp sự cố kỹ thuật. Vui lòng thử lại sau nhé!'; } } /** * Call the external agent API at POST /api/v1/chat */ private async callAgentApi(payload: AgentApiRequest): Promise { const baseUrl = this.configService.get('AGENT_API_URL') || this.configService.get('agent.apiUrl'); const apiKey = this.configService.get('AGENT_API_KEY') || this.configService.get('agent.apiKey'); const url = `${baseUrl}/api/v1/chat`; this.logger.debug(`Agent API URL: ${url}`); this.logger.debug(`Agent API payload: ${JSON.stringify(payload)}`); const headers: Record = { 'Content-Type': 'application/json', }; if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(payload), }); if (!response.ok) { throw new Error(`Agent API returned ${response.status}: ${await response.text()}`); } const data = (await response.json()) as AgentApiResponse; // Support multiple response formats return ( data.reply || data.message || data.content || data.response || data.text || data.answer || '' ); } }