|
|
|
|
|
const https = require('https'); |
|
|
|
|
|
class AIHandler { |
|
|
constructor(apiKeys, memoryHandler = null) { |
|
|
|
|
|
this.apiKeys = Array.isArray(apiKeys) ? apiKeys : [apiKeys]; |
|
|
this.currentKeyIndex = 0; |
|
|
this.memoryHandler = memoryHandler; |
|
|
console.log('AI Handler initialized with' + (memoryHandler ? ' conversation memory' : 'out memory') + ` and ${this.apiKeys.length} API keys`); |
|
|
} |
|
|
|
|
|
|
|
|
getNextApiKey() { |
|
|
const key = this.apiKeys[this.currentKeyIndex]; |
|
|
this.currentKeyIndex = (this.currentKeyIndex + 1) % this.apiKeys.length; |
|
|
return key; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async getAIResponse(userMessage, username = null) { |
|
|
return new Promise((resolve, reject) => { |
|
|
try { |
|
|
console.log(`AI processing message from ${username || 'unknown'}: ${userMessage}`); |
|
|
|
|
|
|
|
|
let historyContext = ''; |
|
|
|
|
|
|
|
|
if (this.memoryHandler && username) { |
|
|
historyContext = this.memoryHandler.getFormattedHistory(username); |
|
|
if (historyContext) { |
|
|
console.log(`Including conversation history for ${username}`); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
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: `; |
|
|
|
|
|
|
|
|
const apiKey = this.getNextApiKey(); |
|
|
|
|
|
|
|
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-05-20:generateContent?key=${apiKey}`; |
|
|
|
|
|
|
|
|
const body = JSON.stringify({ |
|
|
contents: [{ |
|
|
parts: [{ |
|
|
text: prompt |
|
|
}] |
|
|
}], |
|
|
generationConfig: { |
|
|
temperature: 1.0, |
|
|
maxOutputTokens: 5000, |
|
|
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" |
|
|
} |
|
|
] |
|
|
}); |
|
|
|
|
|
|
|
|
console.log(`Sending request to: ${url.replace(apiKey, 'API_KEY')}`); |
|
|
|
|
|
|
|
|
const urlObj = new URL(url); |
|
|
|
|
|
|
|
|
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 = ''; |
|
|
|
|
|
|
|
|
res.on('data', (chunk) => { |
|
|
responseBody += chunk; |
|
|
}); |
|
|
|
|
|
|
|
|
res.on('end', () => { |
|
|
try { |
|
|
|
|
|
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.'); |
|
|
} |
|
|
|
|
|
|
|
|
const response = JSON.parse(responseBody); |
|
|
console.log('API response received, status:', res.statusCode); |
|
|
|
|
|
|
|
|
console.log('Full API response:', responseBody); |
|
|
|
|
|
|
|
|
let text = null; |
|
|
|
|
|
|
|
|
if (response.candidates && response.candidates[0]) { |
|
|
const candidate = response.candidates[0]; |
|
|
|
|
|
if (candidate.content && candidate.content.parts) { |
|
|
|
|
|
const parts = candidate.content.parts; |
|
|
const allText = []; |
|
|
|
|
|
|
|
|
for (const part of parts) { |
|
|
if (part.text) { |
|
|
allText.push(part.text); |
|
|
} |
|
|
} |
|
|
|
|
|
if (allText.length > 0) { |
|
|
|
|
|
text = allText.join('\n'); |
|
|
} |
|
|
} else if (candidate.text) { |
|
|
|
|
|
text = candidate.text; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (text) { |
|
|
|
|
|
console.log(`AI generated text (first 50 chars): ${text.substring(0, 50)}...`); |
|
|
|
|
|
|
|
|
text = text.replace(/^(\"|'|`)/, ''); |
|
|
text = text.replace(/(\"|'|`)$/, ''); |
|
|
text = text.replace(/^Your helpful response: ?/i, ''); |
|
|
|
|
|
|
|
|
text = text.replace(/\*\s+/g, 'β’ '); |
|
|
|
|
|
|
|
|
|
|
|
console.log(`AI generated complete text (${text.length} chars)`); |
|
|
|
|
|
resolve(text); |
|
|
} else { |
|
|
console.error('Could not extract text from response:', JSON.stringify(response)); |
|
|
|
|
|
|
|
|
const fallbacks = [ |
|
|
'μ£μ‘ν©λλ€, λ§μνμ λ΄μ©μ μ λλ‘ μ΄ν΄νμ§ λͺ»νμ΄μ. λ€μ μ§λ¬Έν΄ μ£Όμκ² μ΄μ?', |
|
|
'ν₯λ―Έλ‘μ΄ μ΄μΌκΈ°λ€μ! λ μμΈν μ€λͺ
ν΄ μ£Όμ€λμ?', |
|
|
'μ§λ¬Έμ λν λ΅λ³μ μ°Ύκ³ μμλλ° λ¬Έμ κ° μκ²Όμ΄μ. λ€μ μλν΄ λ³ΌκΉμ?', |
|
|
'λνμ μ°Έμ¬ν΄ μ£Όμ
μ κ°μ¬ν©λλ€. λ€λ₯Έ μ§λ¬Έμ΄ μμΌμ κ°μ?', |
|
|
'μ£μ‘ν©λλ€λ§, μ κ° μ λλ‘ μ²λ¦¬νμ§ λͺ»νμ΅λλ€. λ€λ₯Έ λ°©μμΌλ‘ λ¬Όμ΄λ΄ μ£Όμ€λμ?' |
|
|
]; |
|
|
|
|
|
|
|
|
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('μ£μ‘ν©λλ€, μλ΅μ μ²λ¦¬νλ μ€μ λ¬Έμ κ° λ°μνμ΅λλ€. λ€μ μλν΄ μ£ΌμΈμ.'); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
req.on('error', (error) => { |
|
|
console.error('Request error:', error); |
|
|
resolve('μ£μ‘ν©λλ€, AI μλΉμ€μ μ°κ²°νλ λ° λ¬Έμ κ° μμμ΅λλ€. λμ€μ λ€μ μλν΄ μ£ΌμΈμ.'); |
|
|
}); |
|
|
|
|
|
|
|
|
req.write(body); |
|
|
req.end(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('Unexpected error in getAIResponse:', error); |
|
|
resolve('μ£μ‘ν©λλ€, AI μ²λ¦¬ μ€μ μμμΉ λͺ»ν λ¬Έμ κ° λ°μνμ΅λλ€.'); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
module.exports = AIHandler; |
|
|
|