From 6efb6e587679cd678739a3f8ea1362f34c4b5e4f Mon Sep 17 00:00:00 2001 From: Wroclaw Date: Fri, 26 Apr 2024 05:41:07 +0200 Subject: [PATCH] commands/ask: create Allows to interact with the bot when bot is user installed in discord. --- src/commands/ask.ts | 74 +++++++++++++++++++++++++++++++++++++++++ src/execution.ts | 4 +-- src/toOpenAIMessages.ts | 2 +- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/commands/ask.ts diff --git a/src/commands/ask.ts b/src/commands/ask.ts new file mode 100644 index 0000000..ea39da7 --- /dev/null +++ b/src/commands/ask.ts @@ -0,0 +1,74 @@ +import { + APIApplicationCommandOption +, ApplicationCommandOptionType +, ApplicationCommandType +, ChatInputCommandInteraction +} from "discord.js"; +import { ChatCompletionMessageParam } from "openai/resources"; + +import + Command +,{ApplicationIntegrationType +, InteractionContextTypes +} from "../command"; +import { config } from "../index"; +import { executeChatCompletion, replyInMultiMessage } from "../execution"; +import { formatName } from "../toOpenAIMessages"; + +export default class Ask extends Command implements Command { + name = "ask"; + description = "Promts the bot to reply to a single message without any history context"; + type = ApplicationCommandType.ChatInput; + options: APIApplicationCommandOption[] = [ + { + name: "content", + description: "The content of the prompt", + type: ApplicationCommandOptionType.String, + required: true, + }, + { + name: "ephemeral", + description: "if true, only you can see the response (default true)", + type: ApplicationCommandOptionType.Boolean, + required: false, + } + ]; + integration_types = [ + ApplicationIntegrationType.Guild_Install, + ApplicationIntegrationType.User_Install, + ]; + contexts = [ + InteractionContextTypes.Guild, + InteractionContextTypes.BotDM, + InteractionContextTypes.PrivateChannel, + ]; + + async execute(interaction: ChatInputCommandInteraction) { + const content = interaction.options.getString("content", true); + const ephemeral = interaction.options.getBoolean("ephemeral", false) ?? true; + + if (!interaction.channel && !interaction.channelId) { + console.error("No channel found in interaction"); + console.error(interaction); + await interaction.reply({ + content: "No channel found in interaction???", + ephemeral: true + }); + return; + } + + // TODO: check content in moderation API + + const messages: ChatCompletionMessageParam[] = [ + ...config.systemPrompt(interaction), + { role: "user", name: formatName(interaction.user.displayName), content } + ]; + + const [answer] = await Promise.all([ + executeChatCompletion(messages, interaction), + interaction.deferReply({ ephemeral }), + ]); + + await replyInMultiMessage(answer.choices[0].message.content, interaction); + } +} diff --git a/src/execution.ts b/src/execution.ts index f715564..579a435 100644 --- a/src/execution.ts +++ b/src/execution.ts @@ -315,7 +315,7 @@ async function executeFromQueue(channel: string) { * @param answerContent - The content of the answer. * @param message - The request message to reply to. */ -async function replyInMultiMessage(answerContent: string | null, message: apiRequest) { +export async function replyInMultiMessage(answerContent: string | null, message: apiRequest) { if (answerContent === null || answerContent === "") { if (message instanceof DiscordApi.Message) message.react("😶").catch(() => { }); } @@ -345,7 +345,7 @@ async function replyInMultiMessage(answerContent: string | null, message: apiReq * @param message An optional RequestMessage object representing the request message, used for logging. * @returns A Promise that resolves to the answer from the chat completion process. */ -async function executeChatCompletion( +export async function executeChatCompletion( OpenAImessages: ChatCompletionMessageParam[], message: apiRequest | undefined, ) { diff --git a/src/toOpenAIMessages.ts b/src/toOpenAIMessages.ts index f93d235..54ad00f 100644 --- a/src/toOpenAIMessages.ts +++ b/src/toOpenAIMessages.ts @@ -63,7 +63,7 @@ export function formatMessage(message: DiscordMessage): string { * @param name the name to format * @returns formatted name */ -function formatName(name: string): string { +export function formatName(name: string): string { // replace all characters to ascii equivelant return FoldToAscii.foldReplacing(name) // White spaces are not allowed