Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/binh111afk_whaliostudy.json
T
OpenCode 68a9071cf1 feat: reconcile local master with origin (exa/hot-platform hunters, leaks ledger, latest results)
Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
2026-08-07 12:53:47 +08:00

1 line
16 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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<string>} - 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<string>} - 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<string>} - 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>} - 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};"}}}}