Compare commits
2 commits
03a1c62cd5
...
23ceca5cd3
Author | SHA1 | Date | |
---|---|---|---|
23ceca5cd3 | |||
8ed2e758f8 |
10 changed files with 453 additions and 412 deletions
756
package-lock.json
generated
756
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -15,8 +15,9 @@
|
||||||
"discord.js": "^14.8.0",
|
"discord.js": "^14.8.0",
|
||||||
"fold-to-ascii": "^5.0.1",
|
"fold-to-ascii": "^5.0.1",
|
||||||
"gpt-3-encoder": "^1.1.4",
|
"gpt-3-encoder": "^1.1.4",
|
||||||
"openai": "^3.2.1",
|
"openai": "^4.10.0",
|
||||||
"require-directory": "^2.1.1"
|
"require-directory": "^2.1.1",
|
||||||
|
"typescript": "^5.1.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fold-to-ascii": "^5.0.0",
|
"@types/fold-to-ascii": "^5.0.0",
|
||||||
|
@ -24,7 +25,6 @@
|
||||||
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
||||||
"@typescript-eslint/parser": "^6.2.0",
|
"@typescript-eslint/parser": "^6.2.0",
|
||||||
"eslint": "^8.46.0",
|
"eslint": "^8.46.0",
|
||||||
"prisma": "^5.0.0",
|
"prisma": "^5.0.0"
|
||||||
"typescript": "^5.1.6"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { AutocompleteInteraction, PermissionsBitField } from "discord.js";
|
import { AutocompleteInteraction, PermissionsBitField } from "discord.js";
|
||||||
import { RESTPostAPIApplicationCommandsJSONBody } from "discord.js";
|
import { RESTPostAPIApplicationCommandsJSONBody } from "discord.js";
|
||||||
import { ApplicationCommandOption, ApplicationCommandType, ChatInputCommandInteraction, LocalizationMap, MessageInteraction, PermissionResolvable, UserSelectMenuInteraction } from "discord.js";
|
import { APIApplicationCommandOption, ApplicationCommandType, ChatInputCommandInteraction, LocalizationMap, MessageInteraction, PermissionResolvable, UserSelectMenuInteraction } from "discord.js";
|
||||||
|
|
||||||
type InteractionTypeMap = {
|
type InteractionTypeMap = {
|
||||||
[ApplicationCommandType.ChatInput]: [ChatInputCommandInteraction, string];
|
[ApplicationCommandType.ChatInput]: [ChatInputCommandInteraction, string];
|
||||||
|
@ -13,7 +13,7 @@ interface Command<Type extends keyof InteractionTypeMap = ApplicationCommandType
|
||||||
readonly name_localizations?: LocalizationMap;
|
readonly name_localizations?: LocalizationMap;
|
||||||
readonly description: InteractionTypeMap[Type][1];
|
readonly description: InteractionTypeMap[Type][1];
|
||||||
readonly description_localizations?: LocalizationMap;
|
readonly description_localizations?: LocalizationMap;
|
||||||
readonly options?: ApplicationCommandOption[];
|
readonly options?: APIApplicationCommandOption[];
|
||||||
readonly default_member_permissions?: PermissionResolvable;
|
readonly default_member_permissions?: PermissionResolvable;
|
||||||
readonly type: Type;
|
readonly type: Type;
|
||||||
readonly nsfw?: boolean;
|
readonly nsfw?: boolean;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ApplicationCommandType, ChatInputCommandInteraction, ApplicationCommandOption, ApplicationCommandOptionType, APIEmbedField } from "discord.js";
|
import { ApplicationCommandType, ChatInputCommandInteraction, APIApplicationCommandOption, ApplicationCommandOptionType, APIEmbedField } from "discord.js";
|
||||||
|
|
||||||
import Command from "../command";
|
import Command from "../command";
|
||||||
import { config } from "../index";
|
import { config } from "../index";
|
||||||
|
@ -7,7 +7,7 @@ export default class MyLimit extends Command implements Command {
|
||||||
name = "check-limit";
|
name = "check-limit";
|
||||||
description = "Checks your limit and usage";
|
description = "Checks your limit and usage";
|
||||||
type = ApplicationCommandType.ChatInput;
|
type = ApplicationCommandType.ChatInput;
|
||||||
options: ApplicationCommandOption[] = [
|
options: APIApplicationCommandOption[] = [
|
||||||
{
|
{
|
||||||
name: "recovery-for",
|
name: "recovery-for",
|
||||||
description: "Calculate the limit recovery time for given message count (default: amount required to use the bot again or 1)",
|
description: "Calculate the limit recovery time for given message count (default: amount required to use the bot again or 1)",
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { Message } from "discord.js";
|
import { Message } from "discord.js";
|
||||||
import {
|
import {
|
||||||
ChatCompletionRequestMessage as OpenAIMessage,
|
ChatCompletionMessageParam as OpenAIMessage,
|
||||||
CreateChatCompletionRequest as ChatCompletionRequestData,
|
ChatCompletionCreateParamsNonStreaming as ChatCompletionRequestData,
|
||||||
} from "openai";
|
} from "openai/resources/chat";
|
||||||
|
|
||||||
import IQuota from "./IQuota";
|
import IQuota from "./IQuota";
|
||||||
import MessageCount from "./quota/messageCount";
|
import MessageCount from "./quota/messageCount";
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import DiscordApi, { GuildTextBasedChannel, TextBasedChannel } from "discord.js";
|
import DiscordApi, { GuildTextBasedChannel, TextBasedChannel } from "discord.js";
|
||||||
import { ChatCompletionRequestMessage, ChatCompletionResponseMessage } from "openai";
|
import {APIError as OpenAIError} from "openai";
|
||||||
import Axios from "axios";
|
|
||||||
|
|
||||||
import { database, openai, config } from "./index";
|
import { database, openai, config } from "./index";
|
||||||
import Moderation from "./moderation";
|
import Moderation from "./moderation";
|
||||||
import toOpenAIMessages from "./toOpenAIMessages";
|
import toOpenAIMessages from "./toOpenAIMessages";
|
||||||
import FunctionManager from "./funcitonManager";
|
import FunctionManager from "./funcitonManager";
|
||||||
|
import { ChatCompletion, ChatCompletionMessage, ChatCompletionMessageParam } from "openai/resources/chat";
|
||||||
|
|
||||||
type NonNullableInObject<T, V> = { [k in keyof T]: k extends V ? NonNullable<T[k]> : T[k] };
|
type NonNullableInObject<T, V> = { [k in keyof T]: k extends V ? NonNullable<T[k]> : T[k] };
|
||||||
export type apiRequest = DiscordApi.Message | DiscordApi.RepliableInteraction;
|
export type apiRequest = DiscordApi.Message | DiscordApi.RepliableInteraction;
|
||||||
|
@ -171,12 +171,12 @@ export async function queueRequest(request: apiRequest) {
|
||||||
* @param functionRan counter of how many function have been ran
|
* @param functionRan counter of how many function have been ran
|
||||||
*/
|
*/
|
||||||
function logUsedTokens(
|
function logUsedTokens(
|
||||||
answer: Awaited<ReturnType<typeof openai.createChatCompletion>>,
|
answer: ChatCompletion,
|
||||||
message: RequestMessage,
|
message: RequestMessage,
|
||||||
functionRan: number,
|
functionRan: number,
|
||||||
) {
|
) {
|
||||||
const usage = answer.data.usage;
|
const usage = answer.usage;
|
||||||
const functionName = answer.data.choices[0].message?.function_call?.name;
|
const functionName = answer.choices[0].message?.function_call?.name;
|
||||||
if (usage !== undefined) {
|
if (usage !== undefined) {
|
||||||
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
|
const channelName: string = !message.channel.isDMBased() ? `${message.channel.name} (${message.guild?.name})` : `@${getAuthor(message).tag}`;
|
||||||
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}${functionName ? " [Function: " + functionName + "]" : ""}`);
|
console.log(`Used ${usage.total_tokens} (${usage.prompt_tokens} + ${usage.completion_tokens}) tokens for ${getAuthor(message).tag} (${getAuthor(message).id}) in #${channelName}${functionName ? " [Function: " + functionName + "]" : ""}`);
|
||||||
|
@ -207,7 +207,7 @@ async function executeFromQueue(channel: string) {
|
||||||
const channelQueue = channelsRunning.get(channel) as ChannelsRunningValue;
|
const channelQueue = channelsRunning.get(channel) as ChannelsRunningValue;
|
||||||
const message = channelQueue.at(0) as RequestMessage;
|
const message = channelQueue.at(0) as RequestMessage;
|
||||||
let functionRanCounter = 0;
|
let functionRanCounter = 0;
|
||||||
let OpenAImessages: ChatCompletionRequestMessage[] = [];
|
let OpenAImessages: ChatCompletionMessageParam[] = [];
|
||||||
|
|
||||||
// ignore if we can't even send anything to reply
|
// ignore if we can't even send anything to reply
|
||||||
if (!canReplyToRequest(message)) return;
|
if (!canReplyToRequest(message)) return;
|
||||||
|
@ -234,11 +234,11 @@ async function executeFromQueue(channel: string) {
|
||||||
});
|
});
|
||||||
|
|
||||||
OpenAImessages = toOpenAIMessages(messages.values());
|
OpenAImessages = toOpenAIMessages(messages.values());
|
||||||
let generatedMessage: ChatCompletionResponseMessage | undefined = undefined;
|
let generatedMessage: ChatCompletionMessage | undefined = undefined;
|
||||||
let answer: Awaited<ReturnType<typeof openai.createChatCompletion>>;
|
let answer: Awaited<ReturnType<typeof openai.chat.completions.create>>;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
answer = await openai.createChatCompletion({
|
answer = await openai.chat.completions.create({
|
||||||
...config.chatCompletionParams,
|
...config.chatCompletionParams,
|
||||||
messages: OpenAImessages,
|
messages: OpenAImessages,
|
||||||
// FIXME: don't use new instance of FunctionManager
|
// FIXME: don't use new instance of FunctionManager
|
||||||
|
@ -247,7 +247,7 @@ async function executeFromQueue(channel: string) {
|
||||||
|
|
||||||
logUsedTokens(answer, message, ++functionRanCounter);
|
logUsedTokens(answer, message, ++functionRanCounter);
|
||||||
|
|
||||||
generatedMessage = answer.data.choices[0].message;
|
generatedMessage = answer.choices[0].message;
|
||||||
if (!generatedMessage) throw new Error("Empty message received");
|
if (!generatedMessage) throw new Error("Empty message received");
|
||||||
|
|
||||||
// handle function calls
|
// handle function calls
|
||||||
|
@ -262,9 +262,9 @@ async function executeFromQueue(channel: string) {
|
||||||
|
|
||||||
channelQueue.stopTyping();
|
channelQueue.stopTyping();
|
||||||
|
|
||||||
const answerContent = answer.data.choices[0].message?.content;
|
const answerContent = answer.choices[0].message?.content;
|
||||||
|
|
||||||
if (answerContent === undefined || answerContent === "") {
|
if (answerContent === null || answerContent === "") {
|
||||||
if (message instanceof DiscordApi.Message) message.react("😶").catch(() => {/* GRACEFAIL: It's okay if the bot won't reply */});
|
if (message instanceof DiscordApi.Message) message.react("😶").catch(() => {/* GRACEFAIL: It's okay if the bot won't reply */});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -285,29 +285,37 @@ async function executeFromQueue(channel: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
let errorText: string = "";
|
||||||
channelQueue.stopTyping();
|
channelQueue.stopTyping();
|
||||||
console.error(`Error ocurred while handling chat completion request (${(e as object).constructor.name}):`);
|
if (typeof e !== "object") {
|
||||||
if (Axios.isAxiosError(e)) {
|
console.error(`Error ocurred while handling chat completion request (${typeof e}):`);
|
||||||
console.error(JSON.stringify(e.response?.data));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
if (OpenAImessages.length !== 0) {
|
else if (e === null) {
|
||||||
console.error("Messages:");
|
console.error ("Error ocurred while handling chat completion request: null");
|
||||||
console.error(OpenAImessages);
|
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
console.error(`Error ocurred while handling chat completion request (${e.constructor.name}):`);
|
||||||
|
if (e instanceof OpenAIError) {
|
||||||
|
console.error(JSON.stringify(e));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (OpenAImessages.length !== 0) {
|
||||||
|
console.error("Messages:");
|
||||||
|
console.error(OpenAImessages);
|
||||||
|
}
|
||||||
|
|
||||||
let errorText = "\n";
|
if (e instanceof Error) {
|
||||||
|
errorText = e.message;
|
||||||
if (e instanceof Error) {
|
}
|
||||||
errorText += e.message;
|
else errorText = "";
|
||||||
}
|
if (e instanceof OpenAIError && e.code?.match(/^5..$/) && channelQueue.tries < 3) {
|
||||||
else errorText = "";
|
channelQueue.tries++;
|
||||||
if (Axios.isAxiosError(e) && e.code?.match(/^5..$/) && channelQueue.tries < 3) {
|
await new Promise(r => setTimeout(r, 2000)); // pause for 2 seconds before retrying
|
||||||
channelQueue.tries++;
|
return executeFromQueue(channel);
|
||||||
await new Promise(r => setTimeout(r, 2000)); // pause for 2 seconds before retrying
|
}
|
||||||
return executeFromQueue(channel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
requestReply(
|
requestReply(
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChatCompletionFunctions, ChatCompletionRequestMessage, ChatCompletionRequestMessageFunctionCall } from "openai";
|
import { ChatCompletionCreateParams, ChatCompletionMessage, ChatCompletionMessageParam } from "openai/resources/chat";
|
||||||
|
|
||||||
import { config } from "./index";
|
import { config } from "./index";
|
||||||
|
|
||||||
|
@ -13,6 +13,9 @@ type OpenAIFunctionRequestData<T extends nameTypeMap> = {
|
||||||
[name in keyof T]: T[name];
|
[name in keyof T]: T[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ChatCompletionFunctions = ChatCompletionCreateParams.Function;
|
||||||
|
type ChatCompletionFunctionCall = ChatCompletionMessage.FunctionCall;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the function that can be ran by the OpenAI model
|
* Represents the function that can be ran by the OpenAI model
|
||||||
*/
|
*/
|
||||||
|
@ -61,7 +64,7 @@ export default class FunctionManager {
|
||||||
return rvalue;
|
return rvalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleFunction(request: ChatCompletionRequestMessageFunctionCall): ChatCompletionRequestMessage {
|
public handleFunction(request: ChatCompletionFunctionCall): ChatCompletionMessageParam {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
let parsedArguments: any;
|
let parsedArguments: any;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import DiscordApi from "discord.js";
|
import DiscordApi from "discord.js";
|
||||||
import { Configuration as OpenAIApiConfiguration, OpenAIApi } from "openai";
|
import OpenAIApi from "openai";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import Typescript from "typescript";
|
import Typescript from "typescript";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
@ -43,9 +43,9 @@ function getConfig() {
|
||||||
|
|
||||||
export const config: IConfigRequired = getConfig();
|
export const config: IConfigRequired = getConfig();
|
||||||
|
|
||||||
export const openai = new OpenAIApi(new OpenAIApiConfiguration({
|
export const openai = new OpenAIApi({
|
||||||
apiKey: config.tokens.OpenAI
|
apiKey: config.tokens.OpenAI
|
||||||
}));
|
});
|
||||||
|
|
||||||
export const database = new PrismaClient();
|
export const database = new PrismaClient();
|
||||||
|
|
||||||
|
|
|
@ -28,11 +28,11 @@ export default class Moderation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const answer = await openai.createModeration({
|
const answer = await openai.moderations.create({
|
||||||
input: await formatRequestOrResponse(message),
|
input: await formatRequestOrResponse(message),
|
||||||
});
|
});
|
||||||
|
|
||||||
const flagged = answer.data.results[0].flagged;
|
const flagged = answer.results[0].flagged;
|
||||||
this.cache.set(message.id, flagged);
|
this.cache.set(message.id, flagged);
|
||||||
// FIXME: These next 7 lines does not belong there and should be refactored out.
|
// FIXME: These next 7 lines does not belong there and should be refactored out.
|
||||||
if (flagged) if (message instanceof Message) {
|
if (flagged) if (message instanceof Message) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChatCompletionRequestMessage as OpenAIMessage } from "openai";
|
import { ChatCompletionMessageParam as OpenAIMessage } from "openai/resources/chat";
|
||||||
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
|
import { Collection, Message as DiscordMessage, InteractionResponse } from "discord.js";
|
||||||
import FoldToAscii from "fold-to-ascii";
|
import FoldToAscii from "fold-to-ascii";
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue