initial commit
commit
67b3902bff
@ -0,0 +1,2 @@
|
||||
dist/
|
||||
config.json
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"deno.importMap": "import_map.json",
|
||||
"editor.tabSize": 4,
|
||||
"editor.detectIndentation": false
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
# ai_bot
|
||||
this code should not be used by any sane person, ever.
|
@ -0,0 +1,26 @@
|
||||
{
|
||||
"general": {
|
||||
"name": "GPT-4",
|
||||
"desc": "OpenAI's newest GPT text-generation AI",
|
||||
"ais": ["huggingface"],
|
||||
"db": {
|
||||
"url": "SURREAL_DB_URL_HERE",
|
||||
"user": "USER_HERE",
|
||||
"pass": "PASS_HERE",
|
||||
"namespace": "NS_HERE",
|
||||
"database": "DB_HERE"
|
||||
}
|
||||
},
|
||||
"discord": {
|
||||
"channels": [
|
||||
"CHANNEL_ID_HERE"
|
||||
]
|
||||
},
|
||||
"huggingface": {
|
||||
"tokens": [
|
||||
"TOKEN_HERE"
|
||||
],
|
||||
"memoryLen": 5,
|
||||
"model": "EleutherAI/gpt-neo-2.7B"
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
{
|
||||
"fmt": {
|
||||
"options": {
|
||||
"indentWidth": 4,
|
||||
"lineWidth": 120
|
||||
}
|
||||
},
|
||||
"importMap": "./import_map.json",
|
||||
"tasks": {
|
||||
"run:db": "surreal start --user root --pass root rocksdb://dist/database"
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"imports": {
|
||||
"@wackford/": "https://deno.land/x/wackford@v0.0.1/"
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import { configType } from "../config.ts";
|
||||
import { Channel } from "../database.ts"
|
||||
|
||||
export default class BaseAI {
|
||||
name: string;
|
||||
description: string;
|
||||
history?: string[];
|
||||
memory?: string[];
|
||||
|
||||
constructor(name: string, description: string, config: configType, db: Channel);
|
||||
|
||||
changeShit(opts: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}): void;
|
||||
|
||||
reset(): void;
|
||||
|
||||
complete(username: string, message: string): Promise<string>;
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
class EchoAI {
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
constructor(name: string, description: string, _config: unknown) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
reset() {
|
||||
//NOP
|
||||
}
|
||||
|
||||
changeShit(opts: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}) {
|
||||
this.name = opts.name ?? this.name;
|
||||
this.description = opts.description ?? this.description;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
complete(_username: string, message: string) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
export default EchoAI;
|
@ -0,0 +1,80 @@
|
||||
import { configType } from "../config.ts";
|
||||
import { Channel } from "../database.ts"
|
||||
|
||||
class HuggingFaceAI {
|
||||
name: string;
|
||||
description: string;
|
||||
prefix: string;
|
||||
memory: string[];
|
||||
tokens: string[];
|
||||
tokenNum: number;
|
||||
memoryLen: number;
|
||||
parameters: {
|
||||
max_new_tokens: number;
|
||||
temperature: number;
|
||||
repetition_penalty: number;
|
||||
top_k: number;
|
||||
return_full_text: boolean;
|
||||
};
|
||||
model: string;
|
||||
|
||||
constructor(name: string, description: string, config: configType, db: Channel) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.prefix = `The following is a chat with ${this.name}, ${this.description}.\n`;
|
||||
this.tokens = config.huggingface.tokens;
|
||||
this.tokenNum = 0;
|
||||
this.memoryLen = config.huggingface.memoryLen;
|
||||
this.parameters = {
|
||||
"max_new_tokens": 50,
|
||||
"temperature": 0.8,
|
||||
"repetition_penalty": 1.8,
|
||||
"top_k": 40,
|
||||
"return_full_text": false,
|
||||
};
|
||||
this.model = config.huggingface.model;
|
||||
this.memory = db.history ? db.history.map((m) => `${m.name}: "${m.content}"`) : [];
|
||||
}
|
||||
|
||||
async #query(prompt: string) {
|
||||
const res = await fetch(`https://api-inference.huggingface.co/models/${this.model}`, {
|
||||
body: JSON.stringify({
|
||||
inputs: prompt,
|
||||
parameters: this.parameters,
|
||||
}),
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.tokens[this.tokenNum]}`,
|
||||
},
|
||||
method: "POST",
|
||||
});
|
||||
return await res.json();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.prefix = `The following is a chat with ${this.name}, ${this.description}.\n`;
|
||||
this.memory = [];
|
||||
}
|
||||
|
||||
changeShit(opts: {
|
||||
name?: string;
|
||||
description?: string;
|
||||
}) {
|
||||
this.name = opts.name ?? this.name;
|
||||
this.description = opts.description ?? this.description;
|
||||
this.reset();
|
||||
}
|
||||
|
||||
async complete(username: string, message: string) {
|
||||
console.log(`${username}: "${message}"`);
|
||||
const ctx = this.memory.slice(this.memoryLen * -2);
|
||||
const prompt = `${this.prefix}\n${ctx.join("\n")}\n${username}: "${message}"\n${this.name}: "`;
|
||||
const res = await this.#query(prompt);
|
||||
const botMsg = res[0].generated_text.split('"\n')[0];
|
||||
this.memory.push(`${username}: "${message}"`);
|
||||
this.memory.push(`${this.name}: "${botMsg}"`);
|
||||
console.log(`${this.name}: "${botMsg}"`);
|
||||
return botMsg;
|
||||
}
|
||||
}
|
||||
|
||||
export default HuggingFaceAI;
|
@ -0,0 +1,63 @@
|
||||
import { Bot, Interaction } from "@wackford/discordeno.ts";
|
||||
import { sendInteractionResponse } from "@wackford/mod.ts";
|
||||
import { GoofyAhhException } from "../bot.ts";
|
||||
import config from "../config.ts";
|
||||
import { db, Channel } from "../database.ts";
|
||||
import BaseAI from "./BaseAI.d.ts";
|
||||
import HuggingFaceAI from "./huggingface.ts";
|
||||
|
||||
const dirname = new URL(".", import.meta.url).pathname;
|
||||
|
||||
const AIs: Record<string, BaseAI> = new Proxy({} as Record<string, BaseAI>, {
|
||||
get: (target, prop: string) => {
|
||||
if (!Object.keys(AIs).includes(prop)) {
|
||||
return false;
|
||||
}
|
||||
return target[prop];
|
||||
},
|
||||
});
|
||||
|
||||
export default function initAI() {
|
||||
config.discord.channels.forEach(async (channelId) => {
|
||||
let channel = (await db.select(channelId))[0] as Channel;
|
||||
|
||||
if (!channel) {
|
||||
db.create(channelId, {
|
||||
moduleName: "huggingface",
|
||||
name: config.general.name,
|
||||
description: config.general.desc,
|
||||
history: []
|
||||
})
|
||||
|
||||
channel = (await db.select(channelId))[0] as Channel;
|
||||
}
|
||||
|
||||
console.log(channel)
|
||||
changeAI(channel.moduleName, channelId, channel.name, channel.description)
|
||||
});
|
||||
}
|
||||
|
||||
export async function changeAI(moduleName: string, channelId: string, newName?: string, newDesc?: string) {
|
||||
const channel = (await db.select(channelId))[0] as Channel;
|
||||
|
||||
const name = newName ?? AIs[channelId].name;
|
||||
const desc = newDesc ?? AIs[channelId].description;
|
||||
|
||||
const mod = (await import(dirname + moduleName + ".ts")).default;
|
||||
AIs[channelId] = new mod(name, desc, config, channel);
|
||||
await db.change(channelId, { moduleName: moduleName });
|
||||
}
|
||||
|
||||
export function checkAI(bot: Bot, interaction: Interaction, AI: BaseAI | boolean) {
|
||||
if (!AI) {
|
||||
sendInteractionResponse(bot, interaction, {
|
||||
content: "AI unavailable in this channel; please check your server for the correct channel!",
|
||||
private: true,
|
||||
});
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { AIs };
|
@ -0,0 +1,26 @@
|
||||
import { BotEmitter, initCommands } from "@wackford/mod.ts";
|
||||
import { Intents } from "@wackford/discordeno.ts";
|
||||
import initLocalCommands from "./commands/index.ts";
|
||||
import initOnMessage from "./events/onMessage.ts";
|
||||
import initAIs from "./ai/index.ts";
|
||||
import initDB from "./database.ts";
|
||||
|
||||
export class GoofyAhhException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "GoofyAhhException";
|
||||
}
|
||||
}
|
||||
|
||||
await initDB();
|
||||
initOnMessage();
|
||||
initLocalCommands();
|
||||
initCommands();
|
||||
initAIs();
|
||||
|
||||
await BotEmitter.emit("start", {
|
||||
token: Deno.env.get("TOKEN") ?? (() => {
|
||||
throw new GoofyAhhException("No token?");
|
||||
})(),
|
||||
intents: Intents.Guilds | Intents.GuildMessages | Intents.MessageContent,
|
||||
});
|
@ -0,0 +1,44 @@
|
||||
import { sendInteractionResponse, SlashCommandOptions } from "@wackford/mod.ts";
|
||||
import { ApplicationCommandOptionTypes } from "@wackford/discordeno.ts";
|
||||
import { AIs, checkAI } from "../ai/index.ts";
|
||||
import { db } from "../database.ts";
|
||||
|
||||
export default {
|
||||
name: "change",
|
||||
description: "shows the current name and description of the chatbot",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
description: "sets the name of the bot to this value",
|
||||
type: ApplicationCommandOptionTypes.String,
|
||||
},
|
||||
{
|
||||
name: "description",
|
||||
description: "sets the description of the bot to this value",
|
||||
type: ApplicationCommandOptionTypes.String,
|
||||
},
|
||||
],
|
||||
execute: async (bot, interaction, args) => {
|
||||
const channelId = interaction?.channelId?.toString() as string;
|
||||
const AI = AIs[channelId];
|
||||
const newName = args?.name?.value?.toString() ?? undefined;
|
||||
const newDesc = args?.description?.value?.toString() ?? undefined;
|
||||
|
||||
if (!checkAI(bot, interaction, AI)) return;
|
||||
|
||||
AI.changeShit({
|
||||
name: newName,
|
||||
description: newDesc,
|
||||
});
|
||||
|
||||
db.change(channelId, {
|
||||
name: newName,
|
||||
description: newDesc,
|
||||
history: []
|
||||
})
|
||||
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: `Name: ${newName ?? "[unchanged]"}\nDescription: ${newDesc ?? "[unchanged]"}`,
|
||||
});
|
||||
},
|
||||
} as SlashCommandOptions;
|
@ -0,0 +1,40 @@
|
||||
import { ApplicationCommandOptionChoice, ApplicationCommandOptionTypes } from "@wackford/discordeno.ts";
|
||||
import { sendInteractionResponse, SlashCommandOptions } from "@wackford/mod.ts";
|
||||
import { AIs, changeAI, checkAI } from "../ai/index.ts";
|
||||
import config from "../config.ts";
|
||||
import { db } from "../database.ts";
|
||||
|
||||
export default {
|
||||
name: "changeai",
|
||||
description: "changes the AI",
|
||||
options: [
|
||||
{
|
||||
name: "name",
|
||||
description: "name of the",
|
||||
type: ApplicationCommandOptionTypes.String,
|
||||
required: true,
|
||||
choices: <ApplicationCommandOptionChoice[]> config.general.ais.map((m) =>
|
||||
<ApplicationCommandOptionChoice> {
|
||||
name: m,
|
||||
value: m,
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
execute: async (bot, interaction, args) => {
|
||||
const AI = AIs[interaction?.channelId?.toString() as string];
|
||||
const name = args["name"].value?.toString();
|
||||
|
||||
if (!checkAI(bot, interaction, AI)) return;
|
||||
|
||||
if (name) {
|
||||
await changeAI(name, interaction.channelId?.toString() as string);
|
||||
db.change(interaction?.channelId?.toString() as string, {
|
||||
history: []
|
||||
})
|
||||
}
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: `Changed AI to ${name}!`,
|
||||
});
|
||||
},
|
||||
} as SlashCommandOptions;
|
@ -0,0 +1,17 @@
|
||||
import { sendInteractionResponse, SlashCommandOptions } from "@wackford/mod.ts";
|
||||
import { AIs, checkAI } from "../ai/index.ts";
|
||||
|
||||
export default {
|
||||
name: "debug",
|
||||
description: "shows debug information",
|
||||
execute: async (bot, interaction) => {
|
||||
const AI = AIs[interaction?.channelId?.toString() as string];
|
||||
|
||||
if (!checkAI(bot, interaction, AI)) return;
|
||||
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: `Name: ${AI.name}\nDescription: ${AI.description}\nAI: ${AI.constructor.name}\nHistory: ${JSON.stringify(AI.memory ?? AI.history ?? "[unused]")}`,
|
||||
private: true
|
||||
});
|
||||
},
|
||||
} as SlashCommandOptions;
|
@ -0,0 +1,27 @@
|
||||
import { createSlashCommand, sendInteractionResponse } from "@wackford/mod.ts";
|
||||
|
||||
const dirname = new URL(".", import.meta.url).pathname;
|
||||
|
||||
const helpMessage: string[] = [
|
||||
"/help - helps your idiot ass", // we do this manually because fuck you too
|
||||
];
|
||||
|
||||
export default async function initLocalCommands() {
|
||||
for await (const file of Deno.readDir(dirname)) {
|
||||
if (file.name !== "index.ts") {
|
||||
const mod = (await import(dirname + file.name)).default;
|
||||
helpMessage.push(`/${mod.name} - ${mod.description}`);
|
||||
createSlashCommand(mod);
|
||||
}
|
||||
}
|
||||
createSlashCommand({
|
||||
name: "help",
|
||||
description: "helps your idiot ass",
|
||||
execute: async (bot, interaction) => {
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: helpMessage.join("\n"),
|
||||
private: true,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { sendInteractionResponse, SlashCommandOptions } from "@wackford/mod.ts";
|
||||
import { AIs, checkAI } from "../ai/index.ts";
|
||||
import { db } from "../database.ts";
|
||||
|
||||
export default {
|
||||
name: "reset",
|
||||
description: "resets the conversation history of the chatbot",
|
||||
execute: async (bot, interaction) => {
|
||||
const channelId = interaction?.channelId?.toString() as string;
|
||||
const AI = AIs[interaction?.channelId?.toString() as string];
|
||||
|
||||
if (!checkAI(bot, interaction, AI)) return;
|
||||
|
||||
AI.reset();
|
||||
|
||||
db.change(channelId, {
|
||||
history: []
|
||||
})
|
||||
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: `Done!`,
|
||||
});
|
||||
},
|
||||
} as SlashCommandOptions;
|
@ -0,0 +1,16 @@
|
||||
import { sendInteractionResponse, SlashCommandOptions } from "@wackford/mod.ts";
|
||||
import { AIs, checkAI } from "../ai/index.ts";
|
||||
|
||||
export default {
|
||||
name: "status",
|
||||
description: "shows the current name and description of the chatbot",
|
||||
execute: async (bot, interaction) => {
|
||||
const AI = AIs[interaction?.channelId?.toString() as string];
|
||||
|
||||
if (!checkAI(bot, interaction, AI)) return;
|
||||
|
||||
await sendInteractionResponse(bot, interaction, {
|
||||
content: `Name: ${AI.name}\nDescription: ${AI.description}\nAI: ${AI.constructor.name}`,
|
||||
});
|
||||
},
|
||||
} as SlashCommandOptions;
|
@ -0,0 +1,7 @@
|
||||
import config from "../config.json" assert { type: "json" };
|
||||
|
||||
export type configType = typeof config;
|
||||
|
||||
export { config };
|
||||
|
||||
export default config;
|
@ -0,0 +1,28 @@
|
||||
import Surreal from "https://deno.land/x/surrealdb@v0.5.0/mod.ts";
|
||||
import config from "./config.ts";
|
||||
import { GoofyAhhException } from "./bot.ts";
|
||||
|
||||
export const db = new Surreal(`${config.general.db.url}/rpc`);
|
||||
|
||||
export interface Channel {
|
||||
moduleName: string;
|
||||
name: string;
|
||||
description: string;
|
||||
history: {
|
||||
name: string,
|
||||
content: string
|
||||
}[];
|
||||
}
|
||||
|
||||
export default async function init() {
|
||||
try {
|
||||
await db.signin({
|
||||
user: config.general.db.user,
|
||||
pass: config.general.db.pass
|
||||
});
|
||||
|
||||
await db.use(config.general.db.namespace, config.general.db.database);
|
||||
} catch (e) {
|
||||
throw new GoofyAhhException(e);
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import { db, Channel } from "../database.ts";
|
||||
import { BotEmitter } from "@wackford/mod.ts";
|
||||
import { AIs } from "../ai/index.ts";
|
||||
import config from "../../config.json" assert { type: "json" };
|
||||
|
||||
//const dirname = new URL(".", import.meta.url).pathname;
|
||||
|
||||
export default function init() {
|
||||
BotEmitter.on("message", async (bot, message) => {
|
||||
if (
|
||||
message.content &&
|
||||
!message.content.startsWith("!") &&
|
||||
config.discord.channels.includes(message.channelId.toString()) &&
|
||||
!message.isFromBot &&
|
||||
message.member
|
||||
) {
|
||||
const channel = (await db.select(message.channelId.toString()))[0] as Channel
|
||||
const user = await bot.helpers.getUser(message.member?.id);
|
||||
|
||||
await bot.helpers.startTyping(message.channelId);
|
||||
|
||||
const res = await AIs[message.channelId.toString()].complete(user.username, message.content)
|
||||
|
||||
const history = channel.history ?? []
|
||||
|
||||
history.push({
|
||||
name: user.username,
|
||||
content: message.content
|
||||
}, {
|
||||
name: AIs[message.channelId.toString()].name,
|
||||
content: res
|
||||
})
|
||||
|
||||
db.change(message.channelId.toString(), {
|
||||
history
|
||||
})
|
||||
|
||||
await bot.helpers.sendMessage(message.channelId, {
|
||||
content: `[${AIs[message.channelId.toString()].name}] ${res}`,
|
||||
messageReference: {
|
||||
messageId: message.id,
|
||||
channelId: message.channelId,
|
||||
guildId: message.guildId,
|
||||
failIfNotExists: false,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
Loading…
Reference in new issue