Compare commits
2 Commits
11b3504f28
...
1fb4728b0a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fb4728b0a | ||
|
|
ebf48c7618 |
@@ -1,232 +1,94 @@
|
|||||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||||
import { requestNodeJoinSystem } from "../../modules/socketServerWrappers.mjs";
|
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
||||||
import { getSystemByName } from "../../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
|
||||||
import { getConfig } from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
|
||||||
import { getAvailableTokensInGuild } from "../modules/wrappers.mjs";
|
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
import { OpenAI } from "openai";
|
import { OpenAI } from "openai";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
|
|
||||||
// Initialize environment variables
|
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
const assistant = await openai.beta.assistants.create({
|
||||||
|
name: "Emmelia",
|
||||||
|
instructions: process.env.DRB_SERVER_INITIAL_PROMPT,
|
||||||
|
model: "gpt-4o",
|
||||||
|
});
|
||||||
|
|
||||||
let assistant;
|
class EventHandler extends EventEmitter {
|
||||||
let eventHandler;
|
constructor(client) {
|
||||||
|
super();
|
||||||
(async () => {
|
this.client = client;
|
||||||
try {
|
|
||||||
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
|
||||||
|
|
||||||
assistant = await openai.beta.assistants.create({
|
|
||||||
name: "Emmelia",
|
|
||||||
instructions: (await getConfig("emmeliaInitialPrompt")) || "",
|
|
||||||
model: "gpt-4o-mini",
|
|
||||||
tools: [
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "checkUserVoiceChannel",
|
|
||||||
description: "Check if the user is in a voice channel",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
userId: {
|
|
||||||
type: "string",
|
|
||||||
description: "The ID of the user",
|
|
||||||
},
|
|
||||||
guildId: {
|
|
||||||
type: "string",
|
|
||||||
description: "The ID of the guild",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["userId", "guildId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "getSelectedSystem",
|
|
||||||
description: "Retrieve the selected system details",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
systemName: {
|
|
||||||
type: "string",
|
|
||||||
description: "The name of the system",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["systemName"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "function",
|
|
||||||
function: {
|
|
||||||
name: "joinSelectedSystem",
|
|
||||||
description: "Join the selected system in the user's voice channel",
|
|
||||||
parameters: {
|
|
||||||
type: "object",
|
|
||||||
properties: {
|
|
||||||
userId: {
|
|
||||||
type: "string",
|
|
||||||
description: "The ID of the user",
|
|
||||||
},
|
|
||||||
guildId: {
|
|
||||||
type: "string",
|
|
||||||
description: "The ID of the guild",
|
|
||||||
},
|
|
||||||
systemName: {
|
|
||||||
type: "string",
|
|
||||||
description: "The name of the system",
|
|
||||||
},
|
|
||||||
channelId: {
|
|
||||||
type: "string",
|
|
||||||
description: "The ID of the voice channel",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
required: ["userId", "guildId", "systemName", "channelId"],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
||||||
class EventHandler extends EventEmitter {
|
|
||||||
constructor(client) {
|
|
||||||
super();
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
async onEvent(event) {
|
|
||||||
try {
|
|
||||||
console.log(event);
|
|
||||||
if (event.event === "thread.run.requires_action") {
|
|
||||||
await this.handleRequiresAction(
|
|
||||||
event.data,
|
|
||||||
event.data.id,
|
|
||||||
event.data.thread_id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error handling event:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleRequiresAction(data, runId, threadId) {
|
|
||||||
try {
|
|
||||||
const toolOutputs = await Promise.all(
|
|
||||||
data.required_action.submit_tool_outputs.tool_calls.map(
|
|
||||||
async (toolCall) => {
|
|
||||||
switch (toolCall.function.name) {
|
|
||||||
case "checkUserVoiceChannel":
|
|
||||||
return await this.checkUserVoiceChannel(toolCall);
|
|
||||||
case "getSelectedSystem":
|
|
||||||
return await this.getSelectedSystem(toolCall);
|
|
||||||
case "joinSelectedSystem":
|
|
||||||
return await this.joinSelectedSystem(toolCall);
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Unknown function: ${toolCall.function.name}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error processing required action:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async checkUserVoiceChannel(toolCall) {
|
|
||||||
const { userId, guildId } = JSON.parse(toolCall.function.arguments);
|
|
||||||
const guild = await this.client.guilds.get(guildId);
|
|
||||||
const member = await guild.members.get(userId);
|
|
||||||
const isInVoiceChannel = !!member.voice.channel;
|
|
||||||
|
|
||||||
return {
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
output: JSON.stringify({
|
|
||||||
isInVoiceChannel,
|
|
||||||
channelId: member.voice.channelId,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSelectedSystem(toolCall) {
|
|
||||||
const { systemName } = JSON.parse(toolCall.function.arguments);
|
|
||||||
const system = await getSystemByName(systemName);
|
|
||||||
|
|
||||||
return {
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
output: JSON.stringify(system),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async joinSelectedSystem(toolCall) {
|
|
||||||
const { userId, guildId, systemName, channelId } = JSON.parse(
|
|
||||||
toolCall.function.arguments,
|
|
||||||
);
|
|
||||||
const system = await getSystemByName(systemName);
|
|
||||||
const guild = await this.client.guilds.fetch(guildId);
|
|
||||||
const discordToken = await getAvailableTokensInGuild(guildId);
|
|
||||||
|
|
||||||
if (discordToken) {
|
|
||||||
const result = await requestNodeJoinSystem(
|
|
||||||
system,
|
|
||||||
channelId,
|
|
||||||
discordToken,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
output: JSON.stringify({ success: true, result }),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
tool_call_id: toolCall.id,
|
|
||||||
output: JSON.stringify({
|
|
||||||
success: false,
|
|
||||||
message: "No available bots.",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async submitToolOutputs(toolOutputs, runId, threadId) {
|
|
||||||
try {
|
|
||||||
const stream =
|
|
||||||
this.client.beta.threads.runs.submitToolOutputsStream(
|
|
||||||
threadId,
|
|
||||||
runId,
|
|
||||||
{ tool_outputs: toolOutputs },
|
|
||||||
);
|
|
||||||
for await (const event of stream) {
|
|
||||||
this.emit("event", event);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error submitting tool outputs:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eventHandler = new EventHandler(openai);
|
|
||||||
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Initialization error:", error);
|
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
|
async onEvent(event) {
|
||||||
|
try {
|
||||||
|
console.log(event);
|
||||||
|
// Retrieve events that are denoted with 'requires_action'
|
||||||
|
// since these will have our tool_calls
|
||||||
|
if (event.event === "thread.run.requires_action") {
|
||||||
|
await this.handleRequiresAction(
|
||||||
|
event.data,
|
||||||
|
event.data.id,
|
||||||
|
event.data.thread_id,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error handling event:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleRequiresAction(data, runId, threadId) {
|
||||||
|
try {
|
||||||
|
const toolOutputs =
|
||||||
|
data.required_action.submit_tool_outputs.tool_calls.map((toolCall) => {
|
||||||
|
// Call the function
|
||||||
|
switch (toolCall.function.name) {
|
||||||
|
case "getCurrentTemperature":
|
||||||
|
return {
|
||||||
|
tool_call_id: toolCall.id,
|
||||||
|
output: "57",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Submit all the tool outputs at the same time
|
||||||
|
await this.submitToolOutputs(toolOutputs, runId, threadId);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error processing required action:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async submitToolOutputs(toolOutputs, runId, threadId) {
|
||||||
|
try {
|
||||||
|
// Use the submitToolOutputsStream helper
|
||||||
|
const stream = this.client.beta.threads.runs.submitToolOutputsStream(
|
||||||
|
threadId,
|
||||||
|
runId,
|
||||||
|
{ tool_outputs: toolOutputs },
|
||||||
|
);
|
||||||
|
for await (const event of stream) {
|
||||||
|
this.emit("event", event);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error submitting tool outputs:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventHandler = new EventHandler(openai);
|
||||||
|
eventHandler.on("event", eventHandler.onEvent.bind(eventHandler));
|
||||||
|
|
||||||
export const gptHandler = async (additionalMessages) => {
|
export const gptHandler = async (additionalMessages) => {
|
||||||
|
const thread = await openai.beta.threads.create();
|
||||||
|
|
||||||
|
// Add the additional messages to the conversation
|
||||||
|
for (const msgObj of additionalMessages) {
|
||||||
|
await openai.beta.threads.messages.create(thread.id, msgObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.DEBUG("AI Conversation:", thread);
|
||||||
|
|
||||||
|
// Run the thread to get a response
|
||||||
try {
|
try {
|
||||||
const thread = await openai.beta.threads.create();
|
|
||||||
|
|
||||||
for (const msgObj of additionalMessages) {
|
|
||||||
await openai.beta.threads.messages.create(thread.id, msgObj);
|
|
||||||
}
|
|
||||||
|
|
||||||
const stream = await openai.beta.threads.runs.stream(
|
const stream = await openai.beta.threads.runs.stream(
|
||||||
thread.id,
|
thread.id,
|
||||||
{ assistant_id: assistant.id },
|
{ assistant_id: assistant.id },
|
||||||
@@ -237,8 +99,9 @@ export const gptHandler = async (additionalMessages) => {
|
|||||||
eventHandler.emit("event", event);
|
eventHandler.emit("event", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let response;
|
||||||
const messages = await openai.beta.threads.messages.list(thread.id);
|
const messages = await openai.beta.threads.messages.list(thread.id);
|
||||||
const response = messages.data[0].content[0].text.value;
|
response = messages.data[0].content[0].text.value;
|
||||||
|
|
||||||
log.DEBUG("AI Response:", response);
|
log.DEBUG("AI Response:", response);
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import {
|
|||||||
getConfig,
|
getConfig,
|
||||||
setConfig,
|
setConfig,
|
||||||
} from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
} from "../../modules/mongo-wrappers/mongoConfigWrappers.mjs";
|
||||||
import { ActivityType, PresenceUpdateStatus, Client } from "discord.js";
|
import { ActivityType, PresenceUpdateStatus } from "discord.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control the presence or activity of the discord bot.
|
* Control the presence or activity of the discord bot.
|
||||||
*/
|
*/
|
||||||
class PresenceManager {
|
class PresenceManager {
|
||||||
|
|||||||
Reference in New Issue
Block a user