From ff305769fca68d086299670b836a72d14401088f Mon Sep 17 00:00:00 2001 From: Shadow Date: Mon, 5 Aug 2024 14:18:23 +0000 Subject: [PATCH] Add translation feature to message context menu (#810) * feat(translate): Adds translate feature * cleanup(translate): cleaned up code and comments --------- Co-authored-by: LunaUrsa <1836049+LunaUrsa@users.noreply.github.com> --- src/discord/commands/global/m.translate.ts | 43 ++++++++++++++++ src/global/commands/g.ai.ts | 57 ++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 src/discord/commands/global/m.translate.ts diff --git a/src/discord/commands/global/m.translate.ts b/src/discord/commands/global/m.translate.ts new file mode 100644 index 00000000..59416b6f --- /dev/null +++ b/src/discord/commands/global/m.translate.ts @@ -0,0 +1,43 @@ +import { + ContextMenuCommandBuilder, + Colors, +} from 'discord.js'; +import { + ApplicationCommandType, +} from 'discord-api-types/v10'; +import OpenAI from 'openai'; +import { MessageCommand } from '../../@types/commandDef'; +import { embedTemplate } from '../../utils/embedTemplate'; +import { aiTranslate } from '../../../global/commands/g.ai'; + +const F = f(__filename); + +export const mTranslate: MessageCommand = { + data: new ContextMenuCommandBuilder() + .setName('Translate') + .setType(ApplicationCommandType.Message), + async execute(interaction) { + if (!interaction.guild) return false; + log.info(F, await commandContext(interaction)); + await interaction.deferReply({ ephemeral: true }); + + const targetMessage = interaction.targetMessage.content; + + const messageList = [{ + role: 'user', + content: targetMessage, + }] as OpenAI.Chat.ChatCompletionMessageParam[]; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { response, promptTokens, completionTokens } = await aiTranslate('English', messageList); + await interaction.editReply({ + embeds: [embedTemplate() + .setTitle('Here\'s your translation!') + .setDescription(response) + .setColor(Colors.Blurple)], + }); + return true; + }, +}; + +export default mTranslate; diff --git a/src/global/commands/g.ai.ts b/src/global/commands/g.ai.ts index aa7d41ca..aee01a45 100644 --- a/src/global/commands/g.ai.ts +++ b/src/global/commands/g.ai.ts @@ -1022,3 +1022,60 @@ export async function aiModerate( return moderationAlerts; } + +export async function aiTranslate( + target_language: string, + messages: OpenAI.Chat.ChatCompletionMessageParam [], +):Promise<{ + response: string, + promptTokens: number, + completionTokens: number, + }> { + let response = ''; + let promptTokens = 0; + let completionTokens = 0; + if (!env.OPENAI_API_ORG || !env.OPENAI_API_KEY) return { response, promptTokens, completionTokens }; + + const model = 'gpt-3.5-turbo-1106'; + const chatCompletionMessages = [{ + role: 'system', + content: `You will translate whatever the user sends to their desired language. Their desired language or language code is: ${target_language}.`, + }] as OpenAI.Chat.ChatCompletionMessageParam[]; + chatCompletionMessages.push(...messages); + + const payload = { + model, + messages: chatCompletionMessages, + } as OpenAI.Chat.ChatCompletionCreateParamsNonStreaming; + + // log.debug(F, `payload: ${JSON.stringify(payload, null, 2)}`); + let responseMessage = {} as OpenAI.Chat.ChatCompletionMessageParam; + + const chatCompletion = await openAi.chat.completions + .create(payload) + .catch(err => { + if (err instanceof OpenAI.APIError) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + log.error(F, `${err.name} - ${err.status} - ${err.type} - ${(err.error as any).message} `); // 400 + // log.error (F, `${JSON.stringify(err.headers, null, 2)}`); // {server: 'nginx', ...} + // log.error(F, `${JSON.stringify(err, null, 2)}`); // {server: 'nginx', ...} + } else { + throw err; + } + }); + // log.debug(F, `chatCompletion: ${JSON.stringify(chatCompletion, null, 2)}`); + + if (chatCompletion?.choices[0].message) { + responseMessage = chatCompletion.choices[0].message; + + // Sum up the existing tokens + promptTokens = chatCompletion.usage?.prompt_tokens ?? 0; + completionTokens = chatCompletion.usage?.completion_tokens ?? 0; + + response = responseMessage.content ?? 'Sorry, I\'m not sure how to respond to that.'; + } + + // log.debug(F, `response: ${response}`); + + return { response, promptTokens, completionTokens }; +}