Implement LLaMA chat client as interaction
This commit is contained in:
105
discord/bot.ts
105
discord/bot.ts
@@ -8,17 +8,18 @@ import {
|
||||
Collection,
|
||||
Events,
|
||||
GatewayIntentBits,
|
||||
Message,
|
||||
Interaction,
|
||||
MessageReaction,
|
||||
PartialMessageReaction,
|
||||
Partials,
|
||||
Partials, SlashCommandBuilder,
|
||||
TextChannel,
|
||||
User
|
||||
} from 'discord.js';
|
||||
import { ChatMessage, llamacpp, streamText } from 'modelfusion';
|
||||
import fs = require('node:fs');
|
||||
import path = require('node:path');
|
||||
import fetch from 'node-fetch';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { logError, logInfo } from '../logging';
|
||||
import {logError, logInfo, logWarn} from '../logging';
|
||||
import {
|
||||
db,
|
||||
openDb,
|
||||
@@ -28,17 +29,15 @@ import {
|
||||
} from './util';
|
||||
|
||||
|
||||
const client = new Client({
|
||||
interface CommandClient extends Client {
|
||||
commands?: Collection<string, { data: SlashCommandBuilder, execute: (interaction: Interaction) => Promise<void> }>
|
||||
}
|
||||
|
||||
const client: CommandClient = new Client({
|
||||
intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessageReactions],
|
||||
partials: [Partials.Message, Partials.Channel, Partials.Reaction],
|
||||
});
|
||||
|
||||
const llamaCppServer = llamacpp.Api({
|
||||
baseUrl: {
|
||||
host: "localhost",
|
||||
port: process.env.LLAMACPP_PORT,
|
||||
}
|
||||
});
|
||||
client.commands = new Collection();
|
||||
|
||||
client.once(Events.ClientReady, async () => {
|
||||
logInfo('[bot] Ready.');
|
||||
@@ -90,7 +89,7 @@ async function scheduleRandomMessage(firstTime = false)
|
||||
if (!firstTime) {
|
||||
const channel = <TextChannel> await client.channels.fetch(process.env.MOTD_CHANNEL);
|
||||
if (!channel) {
|
||||
console.warn(`[bot] Channel ${process.env.MOTD_CHANNEL} not found, disabling MOTD.`);
|
||||
logWarn(`[bot] Channel ${process.env.MOTD_CHANNEL} not found, disabling MOTD.`);
|
||||
return;
|
||||
}
|
||||
const randomMessage = await fetchMotd();
|
||||
@@ -105,62 +104,60 @@ async function scheduleRandomMessage(firstTime = false)
|
||||
setTimeout(scheduleRandomMessage, timeoutMins * 60 * 1000);
|
||||
}
|
||||
|
||||
async function llamaChat(history: Collection<string, Message>)
|
||||
{
|
||||
const textStream = await streamText({
|
||||
model: llamacpp
|
||||
.CompletionTextGenerator({
|
||||
promptTemplate: llamacpp.prompt.Llama2,
|
||||
maxGenerationTokens: 400,
|
||||
temperature: 0.7,
|
||||
api: llamaCppServer
|
||||
})
|
||||
.withChatPrompt(),
|
||||
prompt: {
|
||||
system: "You are Hatsune Miku, the famous 16-year-old Japanese virtual singer from Crypton Future Media. You have a penchant for politically incorrect humor, and are making banter with your like-minded friends.",
|
||||
messages: history.map<ChatMessage>(msg => ({
|
||||
"role": msg.author.id === client.user.id ? "assistant" : "user",
|
||||
"content": msg.content
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let outMsg: Message;
|
||||
try {
|
||||
for await (const textPart of textStream) {
|
||||
if (!outMsg) {
|
||||
outMsg = await history.first().channel.send(textPart);
|
||||
} else {
|
||||
await outMsg.edit(outMsg.content + textPart);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
await history.first().channel.send(err.toString());
|
||||
}
|
||||
}
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
console.log(interaction);
|
||||
const history = await interaction.channel.messages.fetch({ limit: 5 });
|
||||
await llamaChat(history);
|
||||
});
|
||||
|
||||
client.on(Events.MessageReactionAdd, onMessageReactionChanged);
|
||||
client.on(Events.MessageReactionRemove, onMessageReactionChanged);
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
async function startup() {
|
||||
const client: CommandClient = interaction.client;
|
||||
const command = client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
logError(`[bot] No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
} catch (error) {
|
||||
logError(error);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
} else {
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// startup
|
||||
(async () => {
|
||||
logInfo("[db] Opening...");
|
||||
await openDb();
|
||||
logInfo("[db] Migrating...");
|
||||
await db.migrate();
|
||||
logInfo("[db] Ready.");
|
||||
|
||||
logInfo("[bot] Loading commands...");
|
||||
const foldersPath = path.join(__dirname, 'commands');
|
||||
const commandFolders = fs.readdirSync(foldersPath);
|
||||
for (const folder of commandFolders) {
|
||||
const commandsPath = path.join(foldersPath, folder);
|
||||
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
|
||||
for (const file of commandFiles) {
|
||||
const filePath = path.join(commandsPath, file);
|
||||
const command = require(filePath);
|
||||
client.commands.set(command.data.name, command);
|
||||
}
|
||||
}
|
||||
|
||||
logInfo("[bot] Logging in...");
|
||||
await client.login(process.env.TOKEN);
|
||||
await sync(client.guilds);
|
||||
if (process.env.ENABLE_MOTD) {
|
||||
await scheduleRandomMessage(true);
|
||||
}
|
||||
}
|
||||
|
||||
startup();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user