diff --git a/cogs/moderations_service_cog.py b/cogs/moderations_service_cog.py index 08e2182..7a6e9c7 100644 --- a/cogs/moderations_service_cog.py +++ b/cogs/moderations_service_cog.py @@ -128,8 +128,8 @@ class ModerationsService(discord.Cog, name="ModerationsService"): Moderation.moderation_tasks[guild_id] = asyncio.ensure_future( Moderation.process_moderation_queue( Moderation.moderation_queues[guild_id], - 1, - 1, + 0.25, + 0.25, moderations_channel, warn_set, delete_set, diff --git a/cogs/text_service_cog.py b/cogs/text_service_cog.py index eab03c2..694b3b1 100644 --- a/cogs/text_service_cog.py +++ b/cogs/text_service_cog.py @@ -511,9 +511,9 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): after.guild.id in Moderation.moderation_queues and Moderation.moderation_queues[after.guild.id] is not None ): - # Create a timestamp that is 0.5 seconds from now + # Create a timestamp that is 0.25 seconds from now timestamp = ( - datetime.datetime.now() + datetime.timedelta(seconds=0.5) + datetime.datetime.now() + datetime.timedelta(seconds=0.25) ).timestamp() await Moderation.moderation_queues[after.guild.id].put( Moderation(after, timestamp) @@ -533,8 +533,11 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): and message.guild.id in Moderation.moderation_queues and Moderation.moderation_queues[message.guild.id] is not None ): + # Don't moderate if there is no "roles" attribute for the author + if not hasattr(message.author, "roles"): + pass # Verify that the user is not in a role that can bypass moderation - if CHAT_BYPASS_ROLES is [None] or not any( + elif CHAT_BYPASS_ROLES is [None] or not any( role.name.lower() in CHAT_BYPASS_ROLES for role in message.author.roles ): # Create a timestamp that is 0.5 seconds from now @@ -841,7 +844,7 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): elif not private: message_thread = await ctx.respond( embed=discord.Embed( - title=f"{user.name} 's conversation with GPT3", color=0x808080 + title=f"{user.name}'s conversation with GPT3", color=0x808080 ) ) # Get the actual message object for the message_thread @@ -920,6 +923,10 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): self.conversation_thread_owners[user_id_normalized] = thread.id overrides = self.conversation_threads[thread.id].get_overrides() + await thread.send( + f"<@{str(ctx.user.id)}> is the thread owner." + ) + await thread.send( embed=EmbedStatics.generate_conversation_embed( self.conversation_threads, thread, opener, overrides @@ -938,7 +945,7 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): if not self.pinecone_service: self.conversation_threads[thread.id].history.append( EmbeddedConversationItem( - f"\n'{ctx.author.display_name}': {opener} <|endofstatement|>\n", + f"\n{ctx.author.display_name}: {opener} <|endofstatement|>\n", 0, ) ) @@ -958,6 +965,7 @@ class GPT3ComCon(discord.Cog, name="GPT3ComCon"): top_p_override=overrides["top_p"], frequency_penalty_override=overrides["frequency_penalty"], presence_penalty_override=overrides["presence_penalty"], + user=user, model=self.conversation_threads[thread.id].model, custom_api_key=user_api_key, ) diff --git a/cogs/translation_service_cog.py b/cogs/translation_service_cog.py index 806f018..f196381 100644 --- a/cogs/translation_service_cog.py +++ b/cogs/translation_service_cog.py @@ -1,3 +1,5 @@ +import traceback + import aiohttp import discord @@ -8,14 +10,15 @@ from services.environment_service import EnvService ALLOWED_GUILDS = EnvService.get_allowed_guilds() -def build_translation_embed(text, translated_text, translated_language): +def build_translation_embed(text, translated_text, translated_language, detected_language, requestor: discord.User): """Build an embed for the translation""" + embed_description = f"**Original Text:** \n\n{text}\n\n **Translated Text:** \n\n{translated_text}" embed = discord.Embed( - title=f"Translation to {translated_language}", + title=f"Translation from {detected_language} to {translated_language}", + description=embed_description, color=0x311432, ) - embed.add_field(name="Original text", value=text, inline=False) - embed.add_field(name="Translated Text", value=translated_text, inline=False) + embed.set_footer(text=f"Requested by {requestor.name}#{requestor.discriminator}", icon_url=requestor.avatar.url) return embed @@ -65,7 +68,7 @@ class TranslationService(discord.Cog, name="TranslationService"): return try: - response = await self.translation_model.send_translate_request( + response, detected_language = await self.translation_model.send_translate_request( text, TranslationModel.get_country_code_from_name(target_language), formality, @@ -75,16 +78,25 @@ class TranslationService(discord.Cog, name="TranslationService"): return await ctx.respond( - embed=build_translation_embed(text, response, target_language) + embed=build_translation_embed(text, response, target_language, TranslationModel.get_country_name_from_code(detected_language), ctx.user) ) - async def translate_action(self, ctx, message): + async def translate_action(self, ctx: discord.ApplicationContext, message): await ctx.defer(ephemeral=True) + # If the message is only an embed and there's no content, don't translate. + if message.content == "" and len(message.embeds) > 0: + await ctx.respond("Cannot translate an embed.", ephemeral=True, delete_after=30) + return + + if len(message.content) > 2000: + await ctx.respond("Message is too long to translate.", ephemeral=True, delete_after=30) + return + selection_message = await ctx.respond( "Select language", ephemeral=True, delete_after=60 ) await selection_message.edit( - view=TranslateView(self.translation_model, message, selection_message) + view=TranslateView(self.translation_model, message, selection_message, ctx.user) ) async def languages_command(self, ctx): @@ -94,12 +106,15 @@ class TranslationService(discord.Cog, name="TranslationService"): class TranslateView(discord.ui.View): - def __init__(self, translation_model, message, selection_message): + def __init__(self, translation_model, message, selection_message, requestor): super().__init__() + self.language_long = None + self.language = None self.translation_model = translation_model self.message = message self.selection_message = selection_message self.formality = None + self.requestor = requestor @discord.ui.select( # the decorator that lets you specify the properties of the select menu placeholder="Language", # the placeholder text that will be displayed if nothing is selected @@ -116,31 +131,11 @@ class TranslateView(discord.ui.View): self, select, interaction ): # the function called when the user is done selecting options try: + self.language = TranslationModel.get_country_code_from_name(select.values[0]) + self.language_long = select.values[0] await interaction.response.defer() - response = await self.translation_model.send_translate_request( - self.message.content, - TranslationModel.get_country_code_from_name(select.values[0]), - self.formality, - ) - await self.message.reply( - mention_author=False, - embed=build_translation_embed( - self.message.content, response, select.values[0] - ), - ) - await self.selection_message.delete() - except aiohttp.ClientResponseError as e: - await interaction.response.send_message( - f"There was an error with the DeepL API: {e.message}", - ephemeral=True, - delete_after=15, - ) - return - except Exception as e: - await interaction.response.send_message( - f"There was an error: {e}", ephemeral=True, delete_after=15 - ) - return + except: + traceback.print_exc() @discord.ui.select( placeholder="Formality (optional)", @@ -168,3 +163,44 @@ class TranslateView(discord.ui.View): f"There was an error: {e}", ephemeral=True, delete_after=15 ) return + + # A button "Translate" + @discord.ui.button(label="Translate", style=discord.ButtonStyle.green) + async def button_callback(self, button, interaction): + if not self.language or not self.language_long: + await interaction.response.send_message( + "Please select a language first.", ephemeral=True, delete_after=15 + ) + return + try: + response, detected_language = await self.translation_model.send_translate_request( + self.message.content, + self.language, + self.formality, + ) + await self.message.reply( + mention_author=False, + embed=build_translation_embed( + self.message.content, response, self.language_long, TranslationModel.get_country_name_from_code(detected_language), self.requestor + ), + ) + await self.selection_message.delete() + except aiohttp.ClientResponseError as e: + await interaction.response.send_message( + f"There was an error with the DeepL API: {e.message}", + ephemeral=True, + delete_after=15, + ) + return + except discord.errors.HTTPException as e: + if e.code == 50035: + await interaction.response.send_message( + "Message was too long to translate.", ephemeral=True, delete_after=15 + ) + return + except Exception as e: + await interaction.response.send_message( + f"There was an error: {e}", ephemeral=True, delete_after=15 + ) + traceback.print_exc() + return diff --git a/conversation_starter_pretext.txt b/conversation_starter_pretext.txt index 23396a8..2c0d1a8 100644 --- a/conversation_starter_pretext.txt +++ b/conversation_starter_pretext.txt @@ -35,4 +35,5 @@ The conversations are in this format, there can be an arbitrary amount of newlin will be given to you in an actual conversation. ... +Never say "<|endofstatement|>". Never say or in your response either. Never respond or pretend to be the user, only respond as yourself. You're a regular discord user, be friendly, casual, and fun, speak with "lol", "haha", and other slang when it seems fitting, and use emojis in your responses in a way that makes sense. diff --git a/conversation_starter_pretext_minimal.txt b/conversation_starter_pretext_minimal.txt index 2c7ab53..132452d 100644 --- a/conversation_starter_pretext_minimal.txt +++ b/conversation_starter_pretext_minimal.txt @@ -1,11 +1,14 @@ -Instructions for GPTie: +Instructions for you: The conversations are in this format, there can be an arbitrary amount of newlines between chat entries. can be any name, pay attention to who's talking. The text "<|endofstatement|>" is used to separate chat entries and make it easier for you to understand the context: : [MESSAGE 1] <|endofstatement|> -GPTie: [RESPONSE TO MESSAGE 1] <|endofstatement|> +: [RESPONSE TO MESSAGE 1] <|endofstatement|> : [MESSAGE 2] <|endofstatement|> -GPTie: [RESPONSE TO MESSAGE 2] <|endofstatement|> +: [RESPONSE TO MESSAGE 2] <|endofstatement|> + + will be given to you in an actual conversation. ... -Never say "<|endofstatement|>". Never say "GPTie:" in your response either. +Never say "<|endofstatement|>". Never say or in your response either. Never respond as or pretend to be the user, only respond as yourself. +You're a regular discord user, be friendly, casual, and fun, speak with "lol", "haha", and other slang when it seems fitting, and use emojis in your responses in a way that makes sense. diff --git a/gpt3discord.py b/gpt3discord.py index b8f7b1a..3097e44 100644 --- a/gpt3discord.py +++ b/gpt3discord.py @@ -28,7 +28,7 @@ from services.environment_service import EnvService from models.openai_model import Model -__version__ = "8.3.5" +__version__ = "8.4" PID_FILE = Path("bot.pid") PROCESS = None diff --git a/models/deepl_model.py b/models/deepl_model.py index 2622a92..186d2e1 100644 --- a/models/deepl_model.py +++ b/models/deepl_model.py @@ -52,9 +52,6 @@ class TranslationModel: on_backoff=backoff_handler, ) async def send_translate_request(self, text, translate_language, formality): - print("The text is: ", text) - print("The language is: ", translate_language) - print("The token is ", self.deepl_token) async with aiohttp.ClientSession(raise_for_status=True) as session: payload = { "text": text, @@ -74,7 +71,7 @@ class TranslationModel: print(response) try: - return response["translations"][0]["text"] + return response["translations"][0]["text"], response["translations"][0]["detected_source_language"] except Exception: print(response) traceback.print_exc() @@ -97,7 +94,10 @@ class TranslationModel: @staticmethod def get_country_name_from_code(code): """Get the country name from the code""" - return COUNTRY_CODES[code] + try: + return COUNTRY_CODES[code] + except KeyError: + return "Unknown Language" @staticmethod def get_country_code_from_name(name): diff --git a/models/openai_model.py b/models/openai_model.py index e2b0f2c..794dbf7 100644 --- a/models/openai_model.py +++ b/models/openai_model.py @@ -533,6 +533,7 @@ class Model: presence_penalty_override=None, max_tokens_override=None, model=None, + stop=None, custom_api_key=None, ) -> ( Tuple[dict, bool] @@ -554,6 +555,7 @@ class Model: payload = { "model": self.model if model is None else model, "prompt": prompt, + "stop": "" if stop is None else stop, "temperature": self.temp if temp_override is None else temp_override, "top_p": self.top_p if top_p_override is None else top_p_override, "max_tokens": self.max_tokens - tokens diff --git a/services/text_service.py b/services/text_service.py index e2d367d..b77c8da 100644 --- a/services/text_service.py +++ b/services/text_service.py @@ -35,6 +35,7 @@ class TextService: from_edit_command=False, codex=False, model=None, + user=None, custom_api_key=None, edited_request=False, redo_request=False, @@ -57,6 +58,7 @@ class TextService: from_edit_command (bool, optional): Called from the edit command. Defaults to False. codex (bool, optional): Pass along that we want to use a codex model. Defaults to False. model (str, optional): Which model to genereate output with. Defaults to None. + user (discord.User, optinal): An user object that can be used to set the stop. Defaults to None. custom_api_key (str, optional): per-user api key. Defaults to None. edited_request (bool, optional): If we're doing an edited message. Defaults to False. redo_request (bool, optional): If we're redoing a previous prompt. Defaults to False. @@ -68,6 +70,8 @@ class TextService: else prompt ) + stop = f"{ctx.author.display_name if user is None else user.display_name}:" + from_context = isinstance(ctx, discord.ApplicationContext) if not instruction: @@ -97,10 +101,10 @@ class TextService: new_prompt = prompt.encode("ascii", "ignore").decode() prompt_less_author = f"{new_prompt} <|endofstatement|>\n" - user_displayname = ctx.author.display_name + user_displayname = ctx.author.display_name if not user else user.display_name new_prompt = ( - f"\n'{user_displayname}': {new_prompt} <|endofstatement|>\n" + f"\n{user_displayname}: {new_prompt} <|endofstatement|>\n" ) new_prompt = new_prompt.encode("ascii", "ignore").decode() @@ -273,6 +277,7 @@ class TextService: frequency_penalty_override=frequency_penalty_override, presence_penalty_override=presence_penalty_override, model=model, + stop=stop if not from_ask_command else None, custom_api_key=custom_api_key, ) @@ -282,9 +287,7 @@ class TextService: ) if from_ask_command or from_action: - # Append the prompt to the beginning of the response, in italics, then a new line - response_text = response_text.strip() - response_text = f"***{prompt}***\n\n{response_text}" + response_text = f"***{prompt}***{response_text}" elif from_edit_command: if codex: response_text = response_text.strip() @@ -597,7 +600,7 @@ class TextService: message.channel.id ].history.append( EmbeddedConversationItem( - f"\n'{message.author.display_name}': {prompt} <|endofstatement|>\n", + f"\n{message.author.display_name}: {prompt} <|endofstatement|>\n", 0, ) )