{"repo": "binh111afk/whaliostudy", "files": {"server/aiService.js": {"7566d8044a32f41b57dff04f4fbcd80aebeb5cdb": {"t": 1786068268.9645522, "n": 14836, "h": "79cfe8c0d7c89c9e", "c": "/**\n * AI Service - Module xử lý gọi AI với Fallback thông minh\n * \n * Luồng hoạt động:\n * 1. Ưu tiên gọi Gemini 2.5 Flash (model chính)\n * 2. Nếu Gemini lỗi 429 (Rate Limit) -> Tự động chuyển sang DeepSeek V3\n * 3. Nếu cả hai đều lỗi -> Trả về thông báo thân thiện\n * \n * @author Whalio Team\n * @version 1.0.0\n */\n\n// ======================== IMPORT LIBRARIES ========================\nconst { GoogleGenerativeAI } = require('@google/generative-ai');\nconst OpenAI = require('openai');\nconst fs = require('fs');\nconst path = require('path');\n\n// ======================== CONSTANTS & CONFIG ========================\n\n/**\n * System Instruction - Định hình tính cách cho bot Whalio\n * Prompt này được đọc từ file whalio_prompt.txt để dễ quản lý\n */\nlet WHALIO_SYSTEM_INSTRUCTION;\n\ntry {\n const promptPath = path.join(__dirname, 'whalio_prompt.txt');\n WHALIO_SYSTEM_INSTRUCTION = fs.readFileSync(promptPath, 'utf8');\n console.log('✅ Đã tải thành công Whalio System Prompt từ file');\n} catch (error) {\n console.warn('⚠️ Không thể đọc file whalio_prompt.txt, sử dụng prompt mặc định:', error.message);\n // Fallback prompt ngắn gọn\n WHALIO_SYSTEM_INSTRUCTION = `\n### DANH TÍNH & VAI TRÒ\nBạn là **Whalio** – Trợ lý AI thân thiện và hài hước của cộng đồng sinh viên Whalio Study.\n\n### NHIỆM VỤ CHÍNH\n1. Hướng dẫn sử dụng các tính năng của website Whalio Study\n2. Tư vấn học tập và đời sống cho sinh viên\n\n### GIỚI HẠN\n- Chỉ hỗ trợ về các tính năng có thật của website\n\n### PHONG CÁCH\n- Thân thiện, hài hước, thấu cảm\n- Sử dụng ngôn ngữ Gen Z phù hợp\n- Đưa ra lời khuyên thẳng thắn nhưng xây dựng\n`;\n}\n\n/**\n * Timeout cho mỗi request (milliseconds)\n * Nếu AI không phản hồi trong 30 giây -> Chuyển sang model dự phòng\n */\nconst REQUEST_TIMEOUT = 30000; // 30 seconds\n\n// ======================== INITIALIZATION ========================\n\n/**\n * Khởi tạo Gemini AI Client\n * API Key lấy từ biến môi trường GEMINI_API_KEY\n */\nlet geminiClient = null;\nlet geminiModel = null;\n\ntry {\n if (process.env.GEMINI_API_KEY) {\n geminiClient = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);\n geminiModel = geminiClient.getGenerativeModel({\n model: \"gemini-2.5-flash\", // Gemini 2.5 Flash - đồng bộ với server.js\n systemInstruction: WHALIO_SYSTEM_INSTRUCTION\n });\n console.log('✅ Gemini AI đã được khởi tạo thành công');\n } else {\n console.warn('⚠️ GEMINI_API_KEY không tồn tại trong .env');\n }\n} catch (error) {\n console.error('❌ Lỗi khởi tạo Gemini:', error.message);\n}\n\n// 2. Khởi tạo Groq (Fallback 1)\nlet groqClient = null;\n\ntry {\n if (process.env.GROQ_API_KEY) {\n groqClient = new OpenAI({\n apiKey: process.env.GROQ_API_KEY,\n baseURL: 'https://api.groq.com/openai/v1'\n });\n console.log('✅ [Layer 2] Groq AI (Llama 3) đã sẵn sàng');\n } else {\n console.warn('⚠️ Chưa cấu hình GROQ_API_KEY');\n }\n} catch (error) {\n console.error('❌ Lỗi khởi tạo Groq:', error.message);\n}\n\n/**\n * Khởi tạo DeepSeek AI Client\n * API Key lấy từ biến môi trường DEEPSEEK_API_KEY\n * DeepSeek sử dụng chuẩn OpenAI API, nên ta dùng thư viện 'openai'\n */\nlet openRouterClient = null;\ntry {\n if (process.env.OPENROUTER_API_KEY) {\n openRouterClient = new OpenAI({\n apiKey: process.env.OPENROUTER_API_KEY,\n baseURL: 'https://openrouter.ai/api/v1',\n // OpenRouter yêu cầu thêm 2 header này để định danh app của ông (để họ biết ai đang dùng Free)\n defaultHeaders: {\n \"HTTP-Referer\": \"https://whalio.com\", // Thay bằng link web của ông (hoặc để vậy cũng được)\n \"X-Title\": \"Whalio Study\",\n }\n });\n console.log('✅ [Layer 3] OpenRouter (Free Models) đã sẵn sàng');\n } else {\n console.warn('⚠️ Chưa cấu hình OPENROUTER_API_KEY');\n }\n} catch (error) {\n console.error('❌ Lỗi khởi tạo OpenRouter:', error.message);\n}\n\n// ======================== CORE FUNCTIONS ========================\n\n/**\n * Gọi Gemini AI để sinh text\n * \n * @param {string} prompt - Câu hỏi/yêu cầu từ người dùng\n * @returns {Promise} - Câu trả lời từ AI\n * @throws {Error} - Ném lỗi nếu gọi API thất bại\n */\nasync function callGemini(prompt) {\n if (!geminiModel) {\n throw new Error('GEMINI_NOT_INITIALIZED');\n }\n\n console.log('🔵 Đang gọi Gemini AI...');\n\n try {\n // Tạo Promise với timeout\n const geminiPromise = geminiModel.generateContent(prompt);\n \n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), REQUEST_TIMEOUT);\n });\n\n // Race giữa API call và timeout\n const result = await Promise.race([geminiPromise, timeoutPromise]);\n const response = await result.response;\n const text = response.text();\n\n console.log('✅ Gemini AI phản hồi thành công');\n return text;\n\n } catch (error) {\n // Xác định loại lỗi\n if (error.message === 'TIMEOUT') {\n console.warn('⏱️ Gemini AI timeout sau 30 giây');\n throw new Error('GEMINI_TIMEOUT');\n }\n\n // Kiểm tra lỗi 429 (Rate Limit)\n if (error.message && (\n error.message.includes('429') ||\n error.message.includes('RESOURCE_EXHAUSTED') ||\n error.message.includes('quota') ||\n error.message.includes('rate limit')\n )) {\n console.warn('⚠️ Gemini AI bị Rate Limit (429)');\n throw new Error('GEMINI_RATE_LIMIT');\n }\n\n // Các lỗi khác\n console.error('❌ Lỗi khi gọi Gemini:', error.message);\n throw new Error(`GEMINI_ERROR: ${error.message}`);\n }\n}\n\n/**\n * Gọi Groq AI (Llama 3) để sinh text\n * * @param {string} prompt - Câu hỏi/yêu cầu từ người dùng\n * @returns {Promise} - Câu trả lời từ AI\n * @throws {Error} - Ném lỗi nếu gọi API thất bại\n */\nasync function callGroq(prompt) {\n if (!groqClient) {\n throw new Error('GROQ_NOT_INITIALIZED');\n }\n\n console.log('🟠 Đang gọi Groq AI (Llama 3)...');\n\n try {\n // Tạo Promise gọi API\n // Lưu ý: Groq dùng SDK của OpenAI nên cú pháp là chat.completions.create\n const groqPromise = groqClient.chat.completions.create({\n model: \"llama-3.3-70b-versatile\", // Model mạnh nhất Free của Groq hiện tại\n messages: [\n { role: \"system\", content: WHALIO_SYSTEM_INSTRUCTION }, // Nhớ đảm bảo biến này đã khai báo ở trên\n { role: \"user\", content: prompt }\n ],\n temperature: 0.7,\n max_tokens: 2048\n });\n \n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), REQUEST_TIMEOUT);\n });\n\n // Race giữa API call và timeout\n const completion = await Promise.race([groqPromise, timeoutPromise]);\n const text = completion.choices[0].message.content;\n\n console.log('✅ Groq AI phản hồi thành công');\n return text;\n\n } catch (error) {\n // Xác định loại lỗi\n if (error.message === 'TIMEOUT') {\n console.warn('⏱️ Groq AI timeout sau 30 giây');\n throw new Error('GROQ_TIMEOUT');\n }\n\n // Kiểm tra lỗi 429 (Rate Limit)\n // Thư viện OpenAI thường trả về error.status hoặc message chứa '429'\n if (error.status === 429 || (error.message && (\n error.message.includes('429') ||\n error.message.includes('rate limit') ||\n error.message.includes('quota') ||\n error.message.includes('Too Many Requests')\n ))) {\n console.warn('⚠️ Groq AI bị Rate Limit (429)');\n throw new Error('GROQ_RATE_LIMIT');\n }\n\n // Các lỗi khác\n console.error('❌ Lỗi khi gọi Groq:', error.message);\n throw new Error(`GROQ_ERROR: ${error.message}`);\n }\n}\n\n/**\n * Gọi OpenRouter AI (Gemma 2 - Free) để sinh text\n * Thay thế cho DeepSeek ở vị trí Fallback 2\n * * @param {string} prompt - Câu hỏi/yêu cầu từ người dùng\n * @returns {Promise} - Câu trả lời từ AI\n * @throws {Error} - Ném lỗi nếu gọi API thất bại\n */\nasync function callOpenRouter(prompt) {\n if (!openRouterClient) {\n throw new Error('OPENROUTER_NOT_INITIALIZED');\n }\n\n console.log('🔵 [3] Đang gọi OpenRouter (Gemma 2 - Free)...');\n\n try {\n // Tạo Promise gọi API OpenRouter\n const openRouterPromise = openRouterClient.chat.completions.create({\n model: \"google/gemma-2-9b-it:free\", // Model miễn phí chất lượng cao của Google\n messages: [\n {\n role: 'system',\n content: WHALIO_SYSTEM_INSTRUCTION // Gửi system prompt\n },\n {\n role: 'user',\n content: prompt\n }\n ],\n // OpenRouter đôi khi yêu cầu referer trong header (đã config lúc init), nhưng thêm vào đây cho chắc nếu cần\n extra_headers: {\n \"HTTP-Referer\": \"https://whalio-study.onrender.com\",\n \"X-Title\": \"Whalio Study\"\n },\n temperature: 0.7,\n max_tokens: 2000\n });\n\n // Tạo Promise Timeout\n const timeoutPromise = new Promise((_, reject) => {\n setTimeout(() => reject(new Error('TIMEOUT')), REQUEST_TIMEOUT);\n });\n\n // Race giữa API call và timeout\n const completion = await Promise.race([openRouterPromise, timeoutPromise]);\n \n // Kiểm tra xem OpenRouter có trả về lỗi trong body không\n if (completion.error) {\n throw new Error(completion.error.message);\n }\n\n const text = completion.choices[0].message.content;\n\n console.log('✅ OpenRouter phản hồi thành công');\n return text;\n\n } catch (error) {\n if (error.message === 'TIMEOUT') {\n console.warn('⏱️ OpenRouter timeout sau 30 giây');\n throw new Error('OPENROUTER_TIMEOUT');\n }\n\n // Kiểm tra lỗi 429 (Rate Limit)\n if (error.status === 429 || (error.message && error.message.includes('429'))) {\n console.warn('⚠️ OpenRouter bị Rate Limit (429)');\n throw new Error('OPENROUTER_RATE_LIMIT');\n }\n\n console.error('❌ Lỗi khi gọi OpenRouter:', error.message);\n throw new Error(`OPENROUTER_ERROR: ${error.message}`);\n }\n}\n\n/**\n * Hàm chính - Gọi AI với Fallback thông minh 3 lớp (3-Layer Defense)\n * * Luồng hoạt động:\n * 1. 🟢 Ưu tiên: Gemini 2.5 Flash (Free Tier)\n * 2. 🟡 Dự phòng 1: Groq (Llama 3 - Free Beta) - Khi Gemini lỗi 429/Timeout\n * 3. 🔴 Dự phòng 2: DeepSeek V3 (Giá rẻ) - Khi cả Gemini và Groq đều sập\n * * @param {string} userMessage - Tin nhắn từ người dùng\n * @returns {Promise} - Object chứa response và metadata\n */\nasync function generateAIResponse(userMessage, imageBase64 = null) {\n // Validate input\n if (!userMessage || typeof userMessage !== 'string' || userMessage.trim() === '') {\n return {\n success: false,\n message: 'Xin lỗi, tôi không nhận được câu hỏi của bạn. Vui lòng thử lại! 😊',\n model: null,\n error: 'INVALID_INPUT'\n };\n }\n\n const startTime = Date.now();\n let response = null;\n let usedModel = null;\n let errorLog = {}; // Lưu lại lỗi để debug nếu cần\n\n // ============ BƯỚC 1: Thử gọi GEMINI (Main) ============\n try {\n response = await callGemini(userMessage);\n usedModel = 'Gemini 2.5 Flash';\n\n return {\n success: true,\n message: response,\n model: usedModel,\n responseTime: Date.now() - startTime,\n error: null\n };\n\n } catch (geminiError) {\n console.warn(`⚠️ Gemini thất bại: ${geminiError.message}`);\n errorLog.gemini = geminiError.message;\n\n // ============ BƯỚC 2: Fallback sang GROQ (Dự phòng 1) ============\n // Chúng ta thử Groq ngay cả khi lỗi không phải là 429 để đảm bảo user luôn có câu trả lời\n console.log('🔄 Đang chuyển sang Groq AI (Llama 3)...');\n\n try {\n response = await callGroq(userMessage);\n usedModel = 'Groq (Llama 3)';\n\n return {\n success: true,\n message: response,\n model: usedModel,\n responseTime: Date.now() - startTime,\n error: null,\n fallback: true // Đánh dấu là đã fallback\n };\n\n } catch (groqError) {\n console.warn(`⚠️ Groq cũng thất bại: ${groqError.message}`);\n errorLog.groq = groqError.message;\n\n // ============ BƯỚC 3: Fallback sang DEEPSEEK (Dự phòng 2 - Chốt chặn cuối) ============\n console.log('🔄 Đang chuyển sang OpenRouter (Gemma 2)...');\n\n try {\n response = await callOpenRouter(userMessage);\n usedModel = 'OpenRouter (Gemma 2)';\n\n return {\n success: true,\n message: response,\n model: usedModel,\n responseTime: Date.now() - startTime,\n error: null,\n fallback: true\n };\n\n } catch (openRouterError) {\n console.error(`❌ OpenRouter cũng thất bại: ${openRouterError.message}`);\n errorLog.openRouter = openRouterError.message;\n\n // ============ CẢ 3 ĐỀU THẤT BẠI ============\n return {\n success: false,\n message: '😔 Hic, hiện tại cả 3 \"bộ não\" của Whalio đều đang quá tải hoặc gặp sự cố. Bạn vui lòng đợi 1-2 phút rồi thử lại nhé!',\n model: null,\n responseTime: Date.now() - startTime,\n error: 'ALL_MODELS_FAILED',\n details: errorLog\n };\n }\n }\n }\n}\n\n/**\n * Kiểm tra trạng thái của các AI services\n * Hữu ích cho việc monitoring và debugging\n * * @returns {Object} - Trạng thái của từng service\n */\nfunction getServiceStatus() {\n return {\n gemini: {\n initialized: geminiModel !== null,\n apiKeyConfigured: !!process.env.GEMINI_API_KEY\n },\n groq: {\n initialized: groqClient !== null,\n apiKeyConfigured: !!process.env.GROQ_API_KEY\n },\n openRouter: {\n initialized: openRouterClient !== null,\n apiKeyConfigured: !!process.env.OPENROUTER_API_KEY\n }\n };\n}\n\n// ======================== EXPORTS ========================\n\nmodule.exports = {\n generateAIResponse, // Hàm chính để gọi AI\n getServiceStatus, // Kiểm tra trạng thái services\n WHALIO_SYSTEM_INSTRUCTION // Export để có thể customize từ bên ngoài nếu cần\n};"}}}}