172 lines
4.6 KiB
JavaScript
172 lines
4.6 KiB
JavaScript
'use strict';
|
|
|
|
const conf = require('../conf');
|
|
const {sleep} = require('../utils');
|
|
const { ProviderFactory } = require('./ai/providers');
|
|
|
|
|
|
class Ai{
|
|
constructor(args){
|
|
this.bot = args.bot;
|
|
this.promptName = args.promptName;
|
|
this.prompCustom = args.prompCustom || '';
|
|
// interval takes precedence over intervalLength (both are valid config names)
|
|
this.intervalLength = args.interval || args.intervalLength || 30;
|
|
this.intervalStop;
|
|
this.messageListener;
|
|
this.provider = null;
|
|
|
|
// Bot-specific AI config (overrides global config)
|
|
// When loaded via config, args contains provider, model, baseUrl, etc. directly
|
|
// When loaded via /ai command, only promptName/prompCustom are passed
|
|
const { bot, promptName, prompCustom, intervalLength, interval, ...configProps } = args;
|
|
this.botConfig = args.botConfig || configProps || {};
|
|
}
|
|
|
|
// Get merged config: bot-specific settings override global settings
|
|
__getConfig() {
|
|
return {
|
|
...conf.ai, // Global defaults
|
|
...this.botConfig, // Bot-specific overrides
|
|
};
|
|
}
|
|
|
|
async init(){
|
|
this.bot.on('onReady', async (argument)=>{
|
|
try{
|
|
await this.start();
|
|
let messages = [''];
|
|
|
|
this.messageListener = this.bot.on('message', (message, type)=>{
|
|
if(type === 'game_info') return;
|
|
if(message.toString().startsWith('<') && message.toString().split('>')[0].includes(this.bot.bot.entity.username)){
|
|
console.log('message blocked from message array')
|
|
return;
|
|
}
|
|
console.log(`Message ${type}: ${message.toString()}`)
|
|
messages.push('>', message.toString());
|
|
});
|
|
|
|
this.intervalStop = setInterval(async ()=>{
|
|
let result;
|
|
|
|
try{
|
|
result = await this.chat(JSON.stringify({
|
|
messages, currentTime:Date.now()+1}
|
|
));
|
|
}catch(error){
|
|
console.log('error AI API', error, result);
|
|
messages = [];
|
|
return ;
|
|
}
|
|
|
|
try{
|
|
messages = [''];
|
|
const responseText = this.provider.getResponse(result);
|
|
if(!responseText) return;
|
|
|
|
// Try to parse JSON response
|
|
try {
|
|
const parsed = JSON.parse(responseText);
|
|
if(Array.isArray(parsed)){
|
|
for(let message of parsed){
|
|
console.log('toSay', message.delay, message.text);
|
|
if(message.text.trim().startsWith('_')) return;
|
|
setTimeout(async (message)=>{
|
|
await this.bot.sayAiSafe(message.text);
|
|
}, 0*1000, message);
|
|
}
|
|
} else {
|
|
throw new Error('Response is not an array');
|
|
}
|
|
} catch(jsonError){
|
|
// JSON parsing failed, treat as plain text
|
|
console.log('JSON parse failed, treating as plain text:', responseText.substring(0, 100));
|
|
// Skip empty responses, underscore signals, and single dash signals
|
|
const text = responseText.trim();
|
|
if(text && text !== '___' && !text.match(/^[-_]+$/)){
|
|
await this.bot.sayAiSafe(text);
|
|
}
|
|
}
|
|
}catch(error){
|
|
console.log('Error in AI message loop', error, result);
|
|
try {
|
|
if(result && this.provider.getResponse(result)){
|
|
console.log(this.provider.getResponse(result))
|
|
}
|
|
} catch(e) {
|
|
// Ignore
|
|
}
|
|
}
|
|
}, this.intervalLength*1000);
|
|
|
|
}catch(error){
|
|
console.log('error in onReady', error);
|
|
}
|
|
});
|
|
}
|
|
|
|
async unload(){
|
|
if(this.intervalStop){
|
|
clearInterval(this.intervalStop);
|
|
this.intervalStop = undefined;
|
|
}
|
|
if(this.messageListener){
|
|
this.messageListener();
|
|
}
|
|
if(this.provider){
|
|
await this.provider.close();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
async start(history){
|
|
const config = this.__getConfig();
|
|
let bulbaItems = {};
|
|
|
|
console.log(`${this.bot.name} AI config:`, {
|
|
provider: config.provider,
|
|
model: config.model,
|
|
promptName: this.promptName,
|
|
baseUrl: config.baseUrl,
|
|
maxOutputTokens: config.maxOutputTokens,
|
|
interval: config.interval,
|
|
});
|
|
|
|
const prompt = conf.ai.prompts[this.promptName](
|
|
this.bot.bot.entity.username,
|
|
config.interval,
|
|
Object.values(this.bot.getPlayers()).map(player=>`<[${player.lvl}] ${player.username}>`).join('\n'),
|
|
bulbaItems,
|
|
this.prompCustom,
|
|
);
|
|
|
|
// Create the provider instance with merged config and prompt
|
|
this.provider = ProviderFactory.create({
|
|
...config,
|
|
prompt: prompt,
|
|
});
|
|
|
|
await this.provider.start(history);
|
|
console.log(`${this.bot.name} AI ${config.provider} provider started (model: ${config.model})`);
|
|
}
|
|
|
|
async chat(message, retryCount=0){
|
|
console.log(`chat ${this.bot.name}`, retryCount)
|
|
try{
|
|
let result = await this.provider.chat(message);
|
|
return result
|
|
}catch(error){
|
|
console.log('AI chat error', error)
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
const AiWeb = require('./ai/web');
|
|
Ai.createRouter = AiWeb.createRouter;
|
|
Ai.webUI = AiWeb.webUI;
|
|
|
|
module.exports = Ai; |