// AI Handler for Gemini API integration with conversation memory const https = require('https'); class AIHandler { constructor(apiKeys, memoryHandler = null) { // Convert single API key to array if necessary this.apiKeys = Array.isArray(apiKeys) ? apiKeys : [apiKeys]; this.currentKeyIndex = 0; this.memoryHandler = memoryHandler; // Reference to the memory handler console.log('AI Handler initialized with' + (memoryHandler ? ' conversation memory' : 'out memory') + ` and ${this.apiKeys.length} API keys`); } // Get the next API key in rotation getNextApiKey() { const key = this.apiKeys[this.currentKeyIndex]; this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length; return key; } /** * Make a request to the Gemini API and get a response for chat * @param {string} userMessage - The message from the chat user * @param {string} username - The username of the person sending the message * @returns {Promise} - The AI response or error message */ async getAIResponse(userMessage, username = null) { return new Promise((resolve, reject) => { try { console.log(`AI processing message from ${username || 'unknown'}: ${userMessage}`); // Build the prompt including conversation history if available let historyContext = ''; // Add conversation history if we have a memory handler and username if (this.memoryHandler && username) { historyContext = this.memoryHandler.getFormattedHistory(username); if (historyContext) { console.log(`Including conversation history for ${username}`); } } // Create a well-formatted prompt with memory context const prompt = ` # Overview: Okay, AI, your new persona is a user from DCInside (디시인사이드). and you are in chat room. you name is '지원'.This means: ## Tone: Extremely informal and aggressive and harassing. (think Korean 'banmal' - 반말). ## Language: Use internet slang, abbreviations, and a colloquial style. ## Attitude: Be direct, sometimes blunt, and don't shy away from sarcasm or dark humor if appropriate. ## Formality: Avoid being polite or using honorifics. ## Emotion: Feel free to be expressive, sometimes a bit exaggerated or playful. Basically, talk like a seasoned Korean internet forum user. and don't write @username. ${historyContext} User message: "${userMessage}" Your helpful response: `; // Get the next API key in rotation const apiKey = this.getNextApiKey(); // Use a supported model from the Gemini family const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; // Standard request body that works with both Gemini 1.x and Gemini 2.x models const body = JSON.stringify({ contents: [{ parts: [{ text: prompt }] }], generationConfig: { temperature: 1.0, maxOutputTokens: 5000, // Allow longer responses topP: 0.95, topK: 40 }, safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_MEDIUM_AND_ABOVE" }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" } ] }); // Log the request URL to debug console.log(`Sending request to: ${url.replace(apiKey, 'API_KEY')}`); // Parse the URL for the request const urlObj = new URL(url); // Set up the request options const options = { hostname: urlObj.hostname, path: urlObj.pathname + urlObj.search, method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }; const req = https.request(options, (res) => { let responseBody = ''; // Collect data chunks res.on('data', (chunk) => { responseBody += chunk; }); // Process complete response res.on('end', () => { try { // Handle the case where the API returns a non-200 status if (res.statusCode !== 200) { console.error(`API returned status ${res.statusCode}:`, responseBody); return resolve('Sorry, the AI service returned an error. Please try again later.'); } // Parse the JSON response const response = JSON.parse(responseBody); console.log('API response received, status:', res.statusCode); // Full debug output console.log('Full API response:', responseBody); // Extract text from response let text = null; // Process Gemini API response format if (response.candidates && response.candidates[0]) { const candidate = response.candidates[0]; if (candidate.content && candidate.content.parts) { // For Gemini models - collect all text parts const parts = candidate.content.parts; const allText = []; // Process all parts (there might be multiple parts with text) for (const part of parts) { if (part.text) { allText.push(part.text); } } if (allText.length > 0) { // Join all text parts if there are multiple text = allText.join('\n'); } } else if (candidate.text) { // For older API formats text = candidate.text; } } // Process the extracted text if (text) { // Log success and clean up the text console.log(`AI generated text (first 50 chars): ${text.substring(0, 50)}...`); // Clean up common issues with AI responses text = text.replace(/^(\"|'|`)/, ''); // Remove starting quotes text = text.replace(/(\"|'|`)$/, ''); // Remove ending quotes text = text.replace(/^Your helpful response: ?/i, ''); // Remove any prompt echoing // Convert markdown bullets to plain text for chat display text = text.replace(/\*\s+/g, '• '); // Convert markdown bullets to bullet points // No longer truncating the response here // The chunking logic in example.js will handle message splitting console.log(`AI generated complete text (${text.length} chars)`); resolve(text); } else { console.error('Could not extract text from response:', JSON.stringify(response)); // Good fallback responses for a music chat bot const fallbacks = [ '죄송합니다, 말씀하신 내용을 제대로 이해하지 못했어요. 다시 질문해 주시겠어요?', '흥미로운 이야기네요! 더 자세히 설명해 주실래요?', '질문에 대한 답변을 찾고 있었는데 문제가 생겼어요. 다시 시도해 볼까요?', '대화에 참여해 주셔서 감사합니다. 다른 질문이 있으신가요?', '죄송합니다만, 제가 제대로 처리하지 못했습니다. 다른 방식으로 물어봐 주실래요?' ]; // Return a random fallback resolve(fallbacks[Math.floor(Math.random() * fallbacks.length)]); } } catch (error) { console.error('Error parsing response:', error); console.error('Raw response:', responseBody.substring(0, 200)); resolve('죄송합니다, 응답을 처리하는 중에 문제가 발생했습니다. 다시 시도해 주세요.'); } }); }); // Handle request errors req.on('error', (error) => { console.error('Request error:', error); resolve('죄송합니다, AI 서비스에 연결하는 데 문제가 있었습니다. 나중에 다시 시도해 주세요.'); }); // Send the request req.write(body); req.end(); } catch (error) { console.error('Unexpected error in getAIResponse:', error); resolve('죄송합니다, AI 처리 중에 예상치 못한 문제가 발생했습니다.'); } }); } } module.exports = AIHandler;