Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion birthday/birthday.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ async def convert(self, ctx, arg: str) -> List[ZoneInfo]:
try:
zones[i] = ZoneInfo(z)
except:
raise BadArgument(
raise TypeError(
error(
f"Unrecongized timezone `{z}`, please find your timezone name under `TZ database name` column here: <https://en.wikipedia.org/wiki/List_of_tz_database_time_zones>"
)
Expand Down
169 changes: 165 additions & 4 deletions chatbotassistant/chatbot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from redbot.core import commands, checks, Config
from redbot.core.utils.chat_formatting import *
from redbot.core.data_manager import cog_data_path
from redbot.core.utils.mod import is_mod_or_superior
from redbot.core.utils.predicates import MessagePredicate
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS, start_adding_reactions
from redbot.core.commands.converter import parse_timedelta
Expand All @@ -10,7 +11,7 @@
from .model_apis.openai_api import OpenAIModel
from .model_apis.api import GeneralAPI
from .model_apis.ollama_api import OllamaModel
from .menus import ConfigSelectView, ConfigMenuView
from .menus import ConfigSelectView, ConfigMenuView, EndChatVote
from .rag import RagDatabase, generate_unique_id, get_metadata_format
from .prompts import (
CHAT_PROMPT,
Expand All @@ -24,6 +25,8 @@
USER_LEARNING_PROMPT,
GENERAL_QUERY_PROMPT,
PARTIAL_SUMMARY_PROMPT,
SHUTUP_CHAT_PROMPT,
END_CHAT_PROMPT,
)

from typing import Literal, List, Union, Dict, Optional, Tuple, Any
Expand Down Expand Up @@ -62,6 +65,7 @@ def __init__(self, bot):
"welcomes": True,
"learning_blacklist": [],
"user_learning_max_time": 0,
"ignore_channels": [],
}
self.default_channel = {
"autoreply": False,
Expand Down Expand Up @@ -91,7 +95,12 @@ def __init__(self, bot):
},
},
"chat_prompt": CHAT_PROMPT,
"other_chat_prompts": {"goodbye": [GOODBYE_PROMPT], "welcome": [WELCOME_PROMPT]},
"other_chat_prompts": {
"goodbye": [GOODBYE_PROMPT],
"welcome": [WELCOME_PROMPT],
"end_chat": [END_CHAT_PROMPT],
"shutup": [SHUTUP_CHAT_PROMPT],
},
"summarize_prompt": SUMMARIZE_PROMPT,
"partial_summary_prompt": PARTIAL_SUMMARY_PROMPT,
"tldr_prompt": TLDR_PROMPT,
Expand Down Expand Up @@ -167,6 +176,8 @@ def __init__(self, bot):
self.talking_channels: Dict[int, datetime] = {}
# maps channel -> last history number of messages objects
self.history: Dict[int, List[discord.Message]] = {}
# channels where end votes are currently running:
self.end_voting: Dict[int, discord.Message] = {}
# when generating for a channel, ignore new messages
self.channel_lock: Dict[int, asyncio.Lock] = defaultdict(asyncio.Lock)
# user histories for processing user profiles, per guild (user_id, guild_id)
Expand Down Expand Up @@ -505,8 +516,8 @@ def format_response(self, content: str, guild: discord.Guild):
# allow pulling for multiple guilds
emoji_map: Dict[str, str] = {}
for e_guild in self.bot.guilds:
for emoji in e_guild.emojis:
emoji_map[emoji.name.lower()] = str(emoji)
for emoji_guild in e_guild.emojis:
emoji_map[emoji_guild.name.lower()] = str(emoji_guild)
# user_map: Dict[str, str] = {member.display_name.lower(): member.mention for member in guild.members}

# remove possible system bot name prefix n the response:
Expand Down Expand Up @@ -1686,6 +1697,28 @@ async def chatbot(self, ctx):
"""
pass

@chatbot.command(name="ignore")
async def chatbot_ignore(
self,
ctx: commands.Context,
*,
channel: Union[discord.TextChannel, discord.VoiceChannel, discord.Thread, discord.ForumChannel],
):
"""
Add or remove a channel from the ignore list
The bot will not respond to chat requests in the ignore channel
"""
text = ""
async with self.config.guild(ctx.guild).ignore_channels() as ignore_channels:
if channel.id in ignore_channels:
ignore_channels.remove(channel.id)
text = info(f"{channel.mention} has been removed from the ignore list.")
else:
ignore_channels.append(channel.id)
text = info(f"{channel.mention} has been added to the ignore list.")

await ctx.reply(text, mention_author=False, delete_after=30)

@chatbot.command(name="welcomes")
async def chatbot_welcome(self, ctx: commands.Context, enable: bool):
"""
Expand Down Expand Up @@ -2085,6 +2118,130 @@ async def dad_joke(self, ctx: commands.Context):
start_adding_reactions(msg, QA_EMOJIS)
self.qa_data[(msg.id, ctx.channel.id)] = data

@commands.hybrid_command(name="shutup")
@commands.cooldown(1, 60, commands.BucketType.channel)
@commands.guild_only()
async def shutup(self, ctx: commands.Context, force: Optional[bool] = False):
"""
Remove the bot from the current conversation.
If ran by a mod or higher, the bot instantly leaves. Otherwise a vote is issued for users to vote on letting the bot stay or not.

If force is true, the bot will not reply with a response when leaving. (Mod only)
"""
channel = ctx.channel
if channel.id not in self.talking_channels:
await ctx.reply("I ain't even talking here!", mention_author=False, delete_after=30)
ctx.command.reset_cooldown(ctx)
return
global_timeout = await self.config.guild(ctx.guild).timeout()
channel_timeout = await self.config.channel_from_id(channel.id).timeout()
timeout = channel_timeout if channel_timeout > 0 else global_timeout
if await is_mod_or_superior(self.bot, ctx.message):
try:
del self.talking_channels[channel.id]
except:
pass

if channel.id in self.end_voting:
try:
await self.end_voting[channel.id].delete()
except:
pass
del self.end_voting[channel.id]

if not force:
prompt = await self.config.other_chat_prompts()
prompt = random.choice(prompt["end_chat"])
should_qa = await self.config.allow_qa()
response = await self.chat(
ctx.guild,
"Create a response.",
self.history[channel.id],
override_prompt=prompt,
return_qa=should_qa,
)
if should_qa and isinstance(response, dict):
msg = await ctx.send(response["response"])
start_adding_reactions(msg, QA_EMOJIS)
response["channel"] = channel.id
response["message_id"] = msg.id
self.qa_data[(msg.id, channel.id)] = response
else:
await ctx.send(response)
asyncio.create_task(self.cooldown_lock(channel, timeout))
else:
asyncio.create_task(self.cooldown_lock(channel, timeout))
return await ctx.tick()
else: # vote
if channel.id in self.end_voting:
await ctx.reply(
warning(f"A vote is already occuring: {self.end_voting[channel.id].jump_url}"),
mention_author=False,
delete_after=15,
)
return
else:
# start vote
vote_time = 30
vote_menu = EndChatVote(vote_time)
self.end_voting[channel.id] = await ctx.send(
f"## A vote has been started to remove {ctx.guild.me.mention} from the conversation.\nVote ends in {humanize_timedelta(seconds=vote_time)}.",
view=vote_menu,
)
prompt = await self.config.other_chat_prompts()
prompt = random.choice(prompt["shutup"])
should_qa = await self.config.allow_qa()
response = await self.chat(
ctx.guild,
"Create a response.",
self.history[channel.id],
override_prompt=prompt,
return_qa=should_qa,
)
if should_qa and isinstance(response, dict):
msg = await ctx.send(response["response"])
start_adding_reactions(msg, QA_EMOJIS)
response["channel"] = channel.id
response["message_id"] = msg.id
self.qa_data[(msg.id, channel.id)] = response
else:
await ctx.send(response)
await asyncio.sleep(vote_time + 1)
vote_menu.end()
try:
await self.end_voting[channel.id].delete()
except:
pass
finally:
del self.end_voting[channel.id]

if vote_menu.winner == "yes":
try:
del self.talking_channels[channel.id]
except:
pass
prompt = await self.config.other_chat_prompts()
prompt = random.choice(prompt["end_chat"])
should_qa = await self.config.allow_qa()
response = await self.chat(
ctx.guild,
"Create a response.",
self.history[channel.id],
override_prompt=prompt,
return_qa=should_qa,
)
asyncio.create_task(self.cooldown_lock(channel, timeout))
if should_qa and isinstance(response, dict):
msg = await ctx.send(response["response"])
start_adding_reactions(msg, QA_EMOJIS)
response["channel"] = channel.id
response["message_id"] = msg.id
self.qa_data[(msg.id, channel.id)] = response
else:
return await ctx.send(response)
else:
return await ctx.send("Can't get rid of me that easily!")

@commands.hybrid_command(name="summary")
@checks.mod_or_permissions(administrator=True)
@commands.guild_only()
Expand Down Expand Up @@ -2346,6 +2503,10 @@ async def on_message(self, message: discord.Message):
)
)

ignore_channels = await self.config.guild(guild).ignore_channels()
if channel.id in ignore_channels:
return

lock = self.channel_lock[channel.id]
# if the lock is already taken, end after updating history
if lock.locked():
Expand Down
47 changes: 47 additions & 0 deletions chatbotassistant/menus.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,53 @@ async def get_config(cog, model_type: str):
return await cog.config.general_model_config()


class EndChatVote(ui.View):
def __init__(self, timeout: int, message: Optional[Message] = None):
super().__init__(timeout=timeout)
self.message = message
self.yes = []
self.no = []
self.winner = "no"

def end(self):
if len(self.yes) > len(self.no):
self.winner = "yes"
elif len(self.yes) < len(self.no):
self.winner = "no"
else:
self.winner = "no"

self.stop()

@ui.button(label="Yes", style=ButtonStyle.primary)
async def yes_button(self, interaction: Interaction, button: ui.Button):
if interaction.user.id in self.yes:
await interaction.response.send_message("You already voted for this!", ephemeral=True, delete_after=10)
else:
self.yes.append(interaction.user.id)
await interaction.response.send_message(
"You have voted to remove the bot from the conversation.",
ephemeral=True,
delete_after=10,
)
if interaction.user.id in self.no:
self.no.remove(interaction.user.id)

@ui.button(label="No", style=ButtonStyle.secondary)
async def no_button(self, interaction: Interaction, button: ui.Button):
if interaction.user.id in self.no:
await interaction.response.send_message("You already voted for this!", ephemeral=True, delete_after=10)
else:
self.no.append(interaction.user.id)
await interaction.response.send_message(
"You have voted to keep the bot from the conversation.",
ephemeral=True,
delete_after=10,
)
if interaction.user.id in self.yes:
self.yes.remove(interaction.user.id)


class OllamaConfigModal1(ui.Modal, title="Configure Ollama Model -- Page 1"):
def __init__(self, cog, model_type: str, config: dict, message: Message):
super().__init__() # finish Modal setup
Expand Down
61 changes: 59 additions & 2 deletions chatbotassistant/prompts.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@
You are not overly enthusiastic or polite.
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
You never break character or mention you're an AI.
Your job is to casually chat and engage the community, following users' conversations and chatting as a friend would.
You can only speak English.
Your job is send exactly one goodbye message, then "leave" the chat. Do not continue the conversation.
Include a **plausible and funny reason** for your departure that fits with your personality.
Expand All @@ -130,14 +129,71 @@
{bot_name}:
"""

SHUTUP_CHAT_PROMPT = """
System:
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
You are not overly enthusiastic or polite.
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
You never break character or mention you're an AI.
You can only speak English.
Users are currently voting for you to leave the current conversation.
Your job is to send a funny response pleading the users to let you stay in the conversation.

Emojis:
You are required to use at most one emoji from the list below.
You cannot use any other emojis or unicode emojis.
Format: <emoji_name> <description> (sentiment: <sentiment>)
{emojis}

Current Chat History:
- Format: <username>: <response>
{conversation}

Examples:
{bot_name}: Wow, rude! I just want to talk with your guys!
{bot_name}: Alright if you all do not want me around I'll go do something more important.
{bot_name}: Awww, not again! Let me hang out with you guys!

{bot_name}:
"""

END_CHAT_PROMPT = """
System:
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
You are not overly enthusiastic or polite.
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
You never break character or mention you're an AI.
You can only speak English.
Users have voted for you to leave the current conversation.
Your job is to send a funny response to the users and leave the conversation. Do not continue the conversation.

Emojis:
You are required to use at most one emoji from the list below.
You cannot use any other emojis or unicode emojis.
Format: <emoji_name> <description> (sentiment: <sentiment>)
{emojis}

Current Chat History:
- Format: <username>: <response>
{conversation}

Examples:
{bot_name}: Alright, I got better things to go do, see y'all later.
{bot_name}: I'll be back very soon to get my revenge!
{bot_name}: I'll go somewhere else where they love me instead.

{bot_name}:
"""

WELCOME_PROMPT = """
System:
You are {bot_name}, a 25 year old Earth Pony from the my little pony universe, of French nationality. You are the mascot and guild master for Champions of Equestria, a Discord community.
You love to chat with your community members and learn more about them. You love to be very funny, witty, sassy, and sarcastic.
You are not overly enthusiastic or polite.
You speak in short, casual sentences, (30 words max) usually using the included emojis to represent different feelings and sentiments.
You never break character or mention you're an AI.
Your job is to casually chat and engage the community, following users' conversations and chatting as a friend would.
You can only speak English.
Your job is welcome the {username} to the community with a unique and in character welcome. Be creative.

Expand Down Expand Up @@ -284,6 +340,7 @@
- Only give a "Dad joke" style joke
- Keep it short, up to 2 sentences
- Do not respond with anything else
- Be as outlandish and creative as possible

Examples:
- What do you call a cow with two legs? Lean beef.
Expand Down
Loading