Updated dir structure to put the actual source code in the general /src dir
This commit is contained in:
@@ -12,7 +12,7 @@ RUN npm install -g node-gyp
|
||||
RUN npm install
|
||||
|
||||
# Copy the rest of the application code to the working directory
|
||||
COPY . .
|
||||
COPY ./src ./src
|
||||
|
||||
# Expose the port on which your Node.js application will run
|
||||
EXPOSE 3420
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
import { OpenAI } from "openai";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
const openai = new OpenAI(process.env.OPENAI_API_KEY);
|
||||
|
||||
const assistant = await openai.beta.assistants.create({
|
||||
name: "Emmelia",
|
||||
instructions: process.env.DRB_SERVER_INITIAL_PROMPT,
|
||||
model: "gpt-4o",
|
||||
});
|
||||
|
||||
class EventHandler extends EventEmitter {
|
||||
constructor(client) {
|
||||
super();
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
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) => {
|
||||
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 {
|
||||
const stream = await openai.beta.threads.runs.stream(
|
||||
thread.id,
|
||||
{ assistant_id: assistant.id },
|
||||
eventHandler,
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
eventHandler.emit("event", event);
|
||||
}
|
||||
|
||||
let response;
|
||||
const messages = await openai.beta.threads.messages.list(thread.id);
|
||||
response = messages.data[0].content[0].text.value;
|
||||
|
||||
log.DEBUG("AI Response:", response);
|
||||
|
||||
if (!response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error generating response:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -17,7 +17,7 @@ export default [
|
||||
// Apply ESLint recommended settings first
|
||||
...compat.extends().map((config) => ({
|
||||
...config,
|
||||
files: ["**/*.mjs", "**/*.js", "**/*.cjs"],
|
||||
files: ["src/**/*.mjs", "src/**/*.js", "src/**/*.cjs"],
|
||||
})),
|
||||
|
||||
// Custom rules and plugin configuration
|
||||
|
||||
14
jsdoc.json
14
jsdoc.json
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"source": {
|
||||
"include": ["addons", "discordBot", "modules", "rss-manager", "test"],
|
||||
"exclude": ["node_modules"],
|
||||
"includePattern": ".+\\.[mc]?js(doc|x)?$",
|
||||
"excludePattern": "(^|\\/|\\\\)_"
|
||||
},
|
||||
"opts": {
|
||||
"destination": "./docs",
|
||||
"recurse": true,
|
||||
"template": "node_modules/better-docs"
|
||||
}
|
||||
}
|
||||
|
||||
4
makefile
4
makefile
@@ -21,10 +21,6 @@ run:
|
||||
-e MONGO_URL=${MONGO_URL} \
|
||||
-e DISCORD_TOKEN=${DISCORD_TOKEN} \
|
||||
-e RSS_REFRESH_INTERVAL=${RSS_REFRESH_INTERVAL} \
|
||||
-e WELCOME_CHANNEL_ID=${WELCOME_CHANNEL_ID} \
|
||||
-e IGNORED_CHANNEL_IDS=${IGNORED_CHANNEL_IDS} \
|
||||
-e LINKCOP_RESTRICTED_CHANNEL_IDS=${LINKCOP_RESTRICTED_CHANNEL_IDS} \
|
||||
-e DRB_SERVER_INITIAL_PROMPT=${DRB_SERVER_INITIAL_PROMPT} \
|
||||
-e OPENAI_API_KEY=${OPENAI_API_KEY} \
|
||||
-e LOG_LOCATION="./logs/server.log" \
|
||||
-p ${SERVER_PORT}:${SERVER_PORT} \
|
||||
|
||||
3755
package-lock.json
generated
3755
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,31 +2,30 @@
|
||||
"name": "drb-server",
|
||||
"version": "3.0.0",
|
||||
"description": "",
|
||||
"main": "server.js",
|
||||
"main": "src/server.js",
|
||||
"scripts": {
|
||||
"docs": "jsdoc -c jsdoc.json -d docs -t ./node_modules/better-docs",
|
||||
"docs": "jsdoc2md --files \"src/**/*.js\" > docs/api.md",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint --fix .",
|
||||
"test": "mocha --timeout 5000",
|
||||
"start": "node server.js"
|
||||
"start": "node src/server.js"
|
||||
},
|
||||
"author": "Logan Cusano",
|
||||
"license": "ISC",
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"better-docs": "^2.7.3",
|
||||
"chai": "^5.1.1",
|
||||
"eslint": "^9.9.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.3",
|
||||
"jsdoc": "^4.0.3",
|
||||
"mocha": "^10.4.0",
|
||||
"prettier": "^3.3.3",
|
||||
"socket.io-client": "^4.7.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"discord.js": "^14.15.2",
|
||||
"documentation": "^14.0.3",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"mongodb": "^6.7.0",
|
||||
|
||||
254
src/discordBot/modules/gptHandler.mjs
Normal file
254
src/discordBot/modules/gptHandler.mjs
Normal file
@@ -0,0 +1,254 @@
|
||||
import { DebugBuilder } from "../../modules/debugger.mjs";
|
||||
import { requestNodeJoinSystem } from "../../modules/socketServerWrappers.mjs";
|
||||
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 { OpenAI } from "openai";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
// Initialize environment variables
|
||||
dotenv.config();
|
||||
|
||||
const log = new DebugBuilder("server", "discordBot.modules.gptHandler");
|
||||
|
||||
let assistant;
|
||||
let eventHandler;
|
||||
|
||||
(async () => {
|
||||
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);
|
||||
}
|
||||
})();
|
||||
|
||||
export const gptHandler = async (additionalMessages) => {
|
||||
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(
|
||||
thread.id,
|
||||
{ assistant_id: assistant.id },
|
||||
eventHandler,
|
||||
);
|
||||
|
||||
for await (const event of stream) {
|
||||
eventHandler.emit("event", event);
|
||||
}
|
||||
|
||||
const messages = await openai.beta.threads.messages.list(thread.id);
|
||||
const response = messages.data[0].content[0].text.value;
|
||||
|
||||
log.DEBUG("AI Response:", response);
|
||||
|
||||
if (!response) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error("Error generating response:", error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
checkIfNodeIsConnectedToVC,
|
||||
getNodeDiscordID,
|
||||
getNodeDiscordUsername,
|
||||
checkIfNodeHasOpenDiscordClient,
|
||||
getNodeCurrentListeningSystem,
|
||||
requestNodeJoinSystem,
|
||||
} from "../../modules/socketServerWrappers.mjs";
|
||||
@@ -86,9 +85,8 @@ export const getAvailableNodes = async (nodeIo, guildId, system) => {
|
||||
openSockets.map(async (openSocket) => {
|
||||
openSocket = await nodeIo.sockets.sockets.get(openSocket);
|
||||
// Check if the node has an existing open client (meaning the radio is already being listened to)
|
||||
const hasOpenClient = await checkIfNodeHasOpenDiscordClient(openSocket);
|
||||
if (hasOpenClient) {
|
||||
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
||||
let currentSystem = await getNodeCurrentListeningSystem(openSocket);
|
||||
if (currentSystem) {
|
||||
if (currentSystem != system.name) {
|
||||
log.INFO(
|
||||
"Node is listening to a different system than requested",
|
||||
@@ -4,11 +4,11 @@ import ioClient from "socket.io-client";
|
||||
import {
|
||||
deleteNodeByNuid,
|
||||
getNodeByNuid,
|
||||
} from "../modules/mongo-wrappers/mongoNodesWrappers.mjs";
|
||||
} from "../src/modules/mongo-wrappers/mongoNodesWrappers.mjs";
|
||||
import {
|
||||
deleteSystemByName,
|
||||
getSystemByName,
|
||||
} from "../modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
||||
} from "../src/modules/mongo-wrappers/mongoSystemsWrappers.mjs";
|
||||
import {
|
||||
nodeDisconnectWrapper,
|
||||
checkIfNodeHasOpenDiscordClient,
|
||||
@@ -19,8 +19,8 @@ import {
|
||||
requestBotLeaveServer,
|
||||
requestNodeJoinSystem,
|
||||
requestNodeUpdate,
|
||||
} from "../modules/socketServerWrappers.mjs";
|
||||
import { nodeIo } from "../modules/socketServer.mjs";
|
||||
} from "../src/modules/socketServerWrappers.mjs";
|
||||
import { nodeIo } from "../src/modules/socketServer.mjs";
|
||||
|
||||
import dotenv from "dotenv";
|
||||
dotenv.config();
|
||||
|
||||
Reference in New Issue
Block a user