rayai/backend-server/backend-server.js

584 lines
21 KiB
JavaScript

import 'dotenv/config'
import express from 'express';
import bodyParser from 'body-parser';
import cmd from 'cmd-promise';
import cors from 'cors';
import cheerio from 'cheerio';
import llamaTokenizer from 'llama-tokenizer-js';
import googleIt from 'google-it';
let name;
let guild;
const prompt = process.env.PROMPT;
const app = express();
const port = 3000;
app.use(cors({
origin: '*',
allowedHeaders: ['Content-Type', 'x-forwarded-for-id', 'x-forwarded-for-name']
}));
app.use(bodyParser.json());
let isProcessing = false;
let conversationHistory = {};
// Helper function to get current timestamp
const getTimestamp = () => {
const now = new Date();
const date = now.toLocaleDateString('en-US');
const time = now.toLocaleTimeString('en-US');
return `${date} [${time}]`;
};
// Middleware to track conversation history by CF-Connecting-IP
app.use((req, res, next) => {
const ip = req.headers['x-forwarded-for-id'] || req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for'] || req.headers['x-real-ip'] || req.ip;
name = req.headers['x-forwarded-for-name'];
guild = req.headers['x-forwarded-for-guild'];
req.clientIp = ip; // Store the IP in a request property
if (!conversationHistory[req.clientIp]) {
if (name) {
if (guild) {
console.log(`${getTimestamp()} [INFO] Incoming request from: ${req.clientIp} | ${name} within ${guild}`); // Log the IP address
console.log(`${getTimestamp()} [INFO] Initializing conversation history for: ${req.clientIp}`);
conversationHistory[req.clientIp] = [{
role: 'system',
content: `My name is: ${name}, We are chatting inside ${guild} a Discord Server. ` + prompt
}];
} else {
conversationHistory[req.clientIp] = [{
role: 'system',
content: `My name is: ${name} ` + prompt
}];
}
} else {
console.log(`${getTimestamp()} [INFO] Incoming request from: ${req.clientIp}`); // Log the IP address
console.log(`${getTimestamp()} [INFO] Initializing conversation history for new IP: ${req.clientIp}`);
conversationHistory[req.clientIp] = [{
role: 'system',
content: prompt
}];
}
}
next();
});
function countLlamaTokens(messages) {
let totalTokens = 0;
for (const message of messages) {
if (message.role === 'user' || message.role === 'assistant') {
const encodedTokens = llamaTokenizer.encode(message.content);
totalTokens += encodedTokens.length;
}
}
return totalTokens;
}
function trimConversationHistory(messages, maxLength, tolerance) {
let tokenLength = countLlamaTokens(messages);
if (tokenLength > maxLength + tolerance) {
const diff = tokenLength - (maxLength + tolerance);
let removedTokens = 0;
// Iterate over the messages in reverse order
for (let i = messages.length - 1; i >= 0; i--) {
const message = messages[i];
const messageTokens = countLlamaTokens([message]);
if (removedTokens + messageTokens <= diff) {
messages.splice(i, 1);
removedTokens += messageTokens;
console.log(`${getTimestamp()} [CLEANUP] ${removedTokens} removed | After Resize: ${countLlamaTokens(messages)}`);
} else {
const messagesToRemove = Math.floor(diff / messageTokens);
for (let j = 0; j < messagesToRemove; j++) {
messages.splice(i, 1);
removedTokens += messageTokens;
}
break;
}
}
}
}
// Function to scrape web page
async function scrapeWebPage(url, length) {
//console.log(`${getTimestamp()} [INFO] Starting to scrape URL: ${url}`);
try {
const res = await fetch(url);
const html = await res.text();
const $ = cheerio.load(html);
// Extract page title, meta description and content
const pageTitle = $('head title').text().trim();
const pageDescription = $('head meta[name="description"]').attr('content');
const pageContent = $('body').text().trim();
// Construct response message with page details
let response = `Title: ${pageTitle}\n`;
if (pageDescription) {
response += `Description: ${pageDescription}\n`;
}
if (pageContent) {
const MAX_CONTENT_LENGTH = length || process.env.MAX_CONTENT_LENGTH || 2000;
let plainTextContent = $('<div>').html(pageContent).text().trim().replace(/[\r\n\t]+/g, ' ');
const codePattern = /\/\/|\/\*|\*\/|\{|\}|\[|\]|\bfunction\b|\bclass\b|\b0x[0-9A-Fa-f]+\b|\b0b[01]+\b/;
const isCode = codePattern.test(plainTextContent);
if (isCode) {
plainTextContent = plainTextContent.replace(codePattern, '');
}
plainTextContent = plainTextContent.replace(/ *\([^)]*\) */g, '');
if (plainTextContent.length > MAX_CONTENT_LENGTH) {
plainTextContent = plainTextContent.substring(0, MAX_CONTENT_LENGTH) + '...';
}
response += `Content: ${plainTextContent.trim()}`;
}
response += `\nURL: ${url}`;
console.log(`${getTimestamp()} [INFO] Successfully scraped URL: ${url}`);
return response;
} catch (err) {
console.error(`${getTimestamp()} [ERROR] Failed to scrape URL: ${url}`, err);
return null;
}
}
app.post('/api/v1/chat', async (req, res) => {
const startTime = Date.now(); // Start time tracking
const ip = req.clientIp;
console.log(`${getTimestamp()} [INFO] Handling chat request from IP: ${ip}`); // Log the IP address
if (isProcessing) {
console.log(`${getTimestamp()} [WARN] System is busy processing another request`);
return res.status(429).json({
message: "Sorry, I am working on another request, try again later"
});
}
isProcessing = true;
try {
let userMessage = req.body.message;
console.log(`${getTimestamp()} [INFO] Received user message: ${userMessage}`);
userMessage = req.body.message + `\nDate/Time:${getTimestamp()}`;
if (!conversationHistory[ip]) {
console.log(`${getTimestamp()} [INFO] Initializing conversation history for new IP: ${ip}`);
conversationHistory[ip] = [{
role: 'system',
content: prompt
}];
}
conversationHistory[ip].push({
role: 'user',
content: userMessage
});
// Trim conversation history if it exceeds the token limit
const maxLength = 14000;
const tolerance = 25;
trimConversationHistory(conversationHistory[ip], maxLength, tolerance);
// Start Plugins ---
const ipRegex = /(\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b)/g;
const ipAddresses = userMessage.match(ipRegex);
const urlRegex = /(https?:\/\/[^\s]+)/g;
const urls = userMessage.match(urlRegex);
const newPersonRegex = /\bnew\s+person\b/gi;
const newPersons = userMessage.match(newPersonRegex);
const whatServersRegex = /\bwhat\s+servers\b/gi;
const myMcRegex = /\bmy-mc\b/gi;
const dlinuxRegex = /\bdlinux\b/gi;
const searchRegex = /\b[Ss]earch\s+(.+)\b/;
const searchMatch = userMessage.match(searchRegex);
// Handle scraping and other plugins asynchronously
const pluginTasks = [];
if (ipAddresses) {
console.log(`${getTimestamp()} [INFO] Detected IP addresses in user message: ${ipAddresses}`);
ipAddresses.forEach(ipAddr => {
pluginTasks.push(handleIPPlugin(ipAddr, ip, conversationHistory));
});
}
if (urls) {
console.log(`${getTimestamp()} [INFO] Detected URLs in user message: ${urls}`);
urls.forEach(url => {
pluginTasks.push(handleURLPlugin(url, ip, conversationHistory));
});
}
if (newPersons) {
console.log(`${getTimestamp()} [INFO] Detected new person request in user message`);
newPersons.forEach(() => {
pluginTasks.push(handleNewPersonPlugin(ip, conversationHistory));
});
}
if (whatServersRegex.test(userMessage)) {
console.log(`${getTimestamp()} [INFO] Detected what servers request in user message`);
pluginTasks.push(handleWhatServersPlugin(ip, conversationHistory));
}
if (myMcRegex.test(userMessage)) {
console.log(`${getTimestamp()} [INFO] Detected My-MC.Link request in user message`);
pluginTasks.push(handleMyMcPlugin(ip, conversationHistory));
}
if (dlinuxRegex.test(userMessage)) {
console.log(`${getTimestamp()} [INFO] Detected dlinux request in user message`);
pluginTasks.push(handleDlinuxPlugin(ip, conversationHistory));
}
if (searchMatch) {
const searchQuery = searchMatch[1];
console.log(`${getTimestamp()} [INFO] Detected search query in user message: ${searchQuery}`);
pluginTasks.push(handleSearchPlugin(searchQuery, ip, conversationHistory));
}
// Wait for all plugin tasks to complete before sending the request to llama
await Promise.all(pluginTasks);
console.log(`${getTimestamp()} [INFO] Sending request to llama API for response`);
const sent = {
model: 'model',
messages: conversationHistory[ip]
};
//console.log(sent);
const llamaResponse = await fetch(`http://127.0.0.1:8002/v1/chat/completions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(sent)
});
const response = await llamaResponse.json();
const assistantMessage = response.choices[0].message;
conversationHistory[ip].push(assistantMessage);
console.log(`${getTimestamp()} [INFO] Received response from llama API`);
console.log(`${getTimestamp()} [DEBUG] Finish Reason: ${response.choices[0].finish_reason}`);
console.log(`${getTimestamp()} [STATS] Usage: prompt_tokens=${response.usage.prompt_tokens}, completion_tokens=${response.usage.completion_tokens}, total_tokens=${response.usage.total_tokens}`);
res.json(assistantMessage);
} catch (error) {
console.error(`${getTimestamp()} [ERROR] An error occurred while handling chat request`, error);
res.status(500).json({
message: "An error occurred",
error: error.message
});
} finally {
isProcessing = false;
const endTime = Date.now(); // End time tracking
const processingTime = ((endTime - startTime) / 1000).toFixed(2); // Calculate processing time in seconds
console.log(`${getTimestamp()} [STATS] Processing Time: ${processingTime} seconds`);
console.log(`${getTimestamp()} [INFO] Finished processing chat request for: ${ip}`);
}
});
// Define plugin handling functions
async function handleIPPlugin(ipAddr, ip, conversationHistory) {
try {
const url = new URL('https://api.abuseipdb.com/api/v2/check');
url.searchParams.append('ipAddress', ipAddr);
url.searchParams.append('maxAgeInDays', '90');
url.searchParams.append('verbose', '');
const options = {
method: 'GET',
headers: {
'Key': process.env.ABUSE_KEY,
'Accept': 'application/json'
}
};
const response = await fetch(url, options);
const data = await response.json();
let abuseResponse = `IP: ${ipAddr}\n`;
abuseResponse += `Abuse Score: ${data.data.abuseConfidenceScore}\n`;
abuseResponse += `Country: ${data.data.countryCode}\n`;
abuseResponse += `Usage Type: ${data.data.usageType}\n`;
abuseResponse += `ISP: ${data.data.isp}\n`;
abuseResponse += `Domain: ${data.data.domain}\n`;
if (data.data.totalReports) {
abuseResponse += `Total Reports: ${data.data.totalReports}\n`;
abuseResponse += `Last Reported: ${data.data.lastReportedAt}\n`;
}
const lastMessageIndex = conversationHistory[ip].length - 1;
if (lastMessageIndex >= 0) {
conversationHistory[ip][lastMessageIndex].content += "\n" + abuseResponse;
console.log(`${getTimestamp()} [INFO] Processed IP address: ${ipAddr}, response: ${abuseResponse}`);
} else {
console.error(`${getTimestamp()} [ERROR] Conversation history is unexpectedly empty for: ${ip}`);
}
} catch (err) {
console.error(`${getTimestamp()} [ERROR] Failed to process IP address: ${ipAddr}`, err);
}
}
async function handleURLPlugin(url, ip, conversationHistory) {
const scrapedContent = await scrapeWebPage(url);
if (scrapedContent) {
conversationHistory[ip].push({
role: 'assistant',
content: scrapedContent
});
console.log(`${getTimestamp()} [INFO] Added scraped content to conversation history for: ${ip}`);
}
}
async function handleNewPersonPlugin(ip, conversationHistory) {
try {
const randomUser = await fetchRandomUser();
if (randomUser) {
let response = `New Person:\n`;
response += `Name: ${randomUser.name.first} ${randomUser.name.last}\n`;
response += `Gender: ${randomUser.gender}\n`;
response += `Location: ${randomUser.location.city}, ${randomUser.location.state}, ${randomUser.location.country}\n`;
response += `Email: ${randomUser.email}\n`;
response += `Phone: ${randomUser.phone}\n`;
// Add more details as needed
conversationHistory[ip].push({
role: 'assistant',
content: response
});
console.log("A request for a new person was made. Response: " + response);
} else {
console.log('Failed to fetch random user.');
}
} catch (err) {
console.error(err);
}
}
async function handleWhatServersPlugin(ip, conversationHistory) {
try {
const response = await fetch(`https://api.my-mc.link/list_all_servers/${process.env.PATH_KEY}/`, {
headers: {
'x-my-mc-auth': process.env.API_KEY
}
});
const data = await response.json();
if (data.success) {
let responseMessage = `Information provided by the system not me: The Current Minecraft Servers online within the My-MC.link P2P JUMP Node System are listed below. These servers are also listed within My-MC Realms.`;
for (const server of data.servers) {
responseMessage += `\nName: ${server.serverName}\n`;
responseMessage += `Game Version: ${server.gameVersion}\n`;
responseMessage += `MOTD: ${server.motd}\n`;
responseMessage += `Online: ${server.online}\n`;
// Add more details as needed
}
conversationHistory[ip].push({
role: 'assistant',
content: responseMessage
});
console.log("A request for server information was made. Response: " + responseMessage);
} else {
console.log('Failed to fetch server information.');
}
} catch (error) {
console.error('Error fetching server information:', error);
}
}
async function handleMyMcPlugin(ip, conversationHistory) {
try {
const response = await fetch('https://my-mc.link/wiki.json');
const data = await response.json();
if (data) {
let responseMessage = `My-MC.Link Wiki:\n`;
// Append all data fields to the response message
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object') {
// Handle nested objects
responseMessage += `${key}:\n`;
Object.keys(data[key]).forEach(innerKey => {
responseMessage += `${innerKey}: ${data[key][innerKey]}\n`;
});
} else {
responseMessage += `${key}: ${data[key]}\n`;
}
});
conversationHistory[ip].push({
role: 'assistant',
content: responseMessage
});
console.log("A request for My-MC.Link wiki information was made.");
} else {
console.log('Failed to fetch My-MC.Link wiki information.');
}
} catch (error) {
console.error('Error fetching My-MC.Link wiki information:', error);
}
}
async function handleDlinuxPlugin(ip, conversationHistory) {
try {
const response = await fetch('https://my-mc.link/dwiki.json');
const data = await response.json();
if (data) {
let responseMessage = `dlinux Wiki:\n`;
// Append all data fields to the response message
Object.keys(data).forEach(key => {
if (typeof data[key] === 'object') {
// Handle nested objects
responseMessage += `${key}:\n`;
Object.keys(data[key]).forEach(innerKey => {
responseMessage += `${innerKey}: ${data[key][innerKey]}\n`;
});
} else {
responseMessage += `${key}: ${data[key]}\n`;
}
});
conversationHistory[ip].push({
role: 'assistant',
content: responseMessage
});
console.log("A request for dlinux wiki information was made.");
} else {
console.log('Failed to fetch dlinux wiki information.');
}
} catch (error) {
console.error('Error fetching dlinux wiki information:', error);
}
}
async function handleSearchPlugin(searchQuery, ip, conversationHistory) {
const options = {
query: searchQuery,
limit: 5,
disableConsole: true
};
try {
const results = await googleIt(options);
let searchResponse = `Search Query: ${searchQuery}\n`;
searchResponse += `Top Google search results:\n`;
let scrapedContent = '';
for (let i = 0; i < results.length; i++) {
const result = results[i];
searchResponse += `${i + 1}. ${result.title} - ${result.link}\n`;
try {
const scrapeResult = await scrapeWebPage(result.link, 800);
searchResponse += `Scraped Data: ${scrapeResult}\n`;
scrapedContent += `Scraped Data from ${result.link}:\n${scrapeResult}\n`;
} catch (scrapeErr) {
console.error(`${getTimestamp()} [ERROR] Failed to scrape URL: ${result.link}`, scrapeErr);
searchResponse += `Failed to scrape URL: ${result.link}\n`;
scrapedContent += `Failed to scrape URL: ${result.link}\n`;
}
}
const lastMessageIndex = conversationHistory[ip].length - 1;
if (lastMessageIndex >= 0) {
conversationHistory[ip][lastMessageIndex].content += "\nYou scraped these results, generate a detailed report, Also provide the list of the scraped URLs in your report. \n" + searchResponse;
console.log(`${getTimestamp()} [INFO] Processed search query: ${searchQuery}.`);
} else {
console.error(`${getTimestamp()} [ERROR] Conversation history is unexpectedly empty for: ${ip}`);
}
if (scrapedContent) {
conversationHistory[ip].push({
role: 'assistant',
content: scrapedContent
});
console.log(`${getTimestamp()} [INFO] Added scraped content to conversation history for: ${ip}`);
}
} catch (err) {
console.error(`${getTimestamp()} [ERROR] Failed to perform Google search: ${searchQuery}`, err);
}
}
app.get('/api/v1/conversation-history', (req, res) => {
const ip = req.clientIp;
console.log(`${getTimestamp()} [INFO] Fetching conversation history for: ${ip}`); // Log the IP address
res.json(conversationHistory[ip]);
});
app.post('/api/v1/restart-core', (req, res) => {
console.log(`${getTimestamp()} [INFO] Restarting core service`);
cmd(`docker restart llama-gpu-server`).then(out => {
console.log(`${getTimestamp()} [INFO] Core service restarted`);
res.json(out.stdout);
}).catch(err => {
console.error(`${getTimestamp()} [ERROR] Failed to restart core service`, err);
res.status(500).json({
message: "An error occurred while restarting the core service",
error: err.message
});
});
});
app.post('/api/v1/reset-conversation', (req, res) => {
const ip = req.clientIp;
console.log(name)
console.log(`${getTimestamp()} [INFO] Resetting conversation history for: ${ip}`); // Log the IP address
if (name) {
if (guild) {
console.log(`${getTimestamp()} [INFO] Incoming request from: ${req.clientIp} | ${name} within ${guild}`); // Log the IP address
console.log(`${getTimestamp()} [INFO] Initializing conversation history for: ${req.clientIp}`);
conversationHistory[req.clientIp] = [{
role: 'system',
content: `My name is: ${name}, We are chatting inside ${guild} a Discord Server. ` + prompt
}];
} else {
conversationHistory[req.clientIp] = [{
role: 'system',
content: `My name is: ${name} ` + prompt
}];
}
} else {
console.log(`${getTimestamp()} [INFO] Incoming request from: ${req.clientIp}`); // Log the IP address
console.log(`${getTimestamp()} [INFO] Initializing conversation history for new IP: ${req.clientIp}`);
conversationHistory[req.clientIp] = [{
role: 'system',
content: prompt
}];
}
console.log(`${getTimestamp()} [INFO] Conversation history reset for: ${ip}`);
res.json({
message: "Conversation history reset for: " + ip
});
});
app.listen(port, () => {
console.log(`${getTimestamp()} [INFO] Server running at http://localhost:${port}`);
});