Implement quiet mode (not responding to everything)
More README content "Fixing" auto-joining
This commit is contained in:
parent
1ed989c9b9
commit
85a04c4188
4 changed files with 102 additions and 27 deletions
52
README.md
52
README.md
|
@ -9,20 +9,20 @@ probably add more in the future, so the name is a bit misleading.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- AI-generated responses to all messages in a Matrix room (chatbot)
|
- AI-generated responses to messages in a Matrix room (chatbot)
|
||||||
- Currently supports OpenAI (tested with `gpt-3.5-turbo` and `gpt-4`)
|
- Currently supports OpenAI (tested with `gpt-3.5-turbo` and `gpt-4`)
|
||||||
- AI-generated pictures via the `!gptbot imagine` command
|
- AI-generated pictures via the `!gptbot imagine` command
|
||||||
- Currently supports OpenAI (DALL-E)
|
- Currently supports OpenAI (DALL-E)
|
||||||
- Mathematical calculations via the `!gptbot calculate` command
|
- Mathematical calculations via the `!gptbot calculate` command
|
||||||
- Currently supports WolframAlpha
|
- Currently supports WolframAlpha
|
||||||
|
- Automatic classification of messages (for `imagine`, `calculate`, etc.)
|
||||||
|
- Beta feature, see Usage section for details
|
||||||
- Really useful commands like `!gptbot help` and `!gptbot coin`
|
- Really useful commands like `!gptbot help` and `!gptbot coin`
|
||||||
- DuckDB database to store room context
|
- DuckDB database to store room context
|
||||||
|
|
||||||
## Planned features
|
## Planned features
|
||||||
|
|
||||||
- End-to-end encryption support (partly implemented, but not yet working)
|
- End-to-end encryption support (partly implemented, but not yet working)
|
||||||
- Automatic classification of messages (for `imagine`, `calculate`, etc.)
|
|
||||||
- Beta feature, enable for a room using `!gptbot roomsettings classification true`
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
@ -57,12 +57,48 @@ to messages. If you want to create a new room, you can use the `!gptbot newroom`
|
||||||
command at any time, which will cause the bot to create a new room and invite
|
command at any time, which will cause the bot to create a new room and invite
|
||||||
you to it. You may also specify a room name, e.g. `!gptbot newroom My new room`.
|
you to it. You may also specify a room name, e.g. `!gptbot newroom My new room`.
|
||||||
|
|
||||||
Note that the bot will currently respond to _all_ messages in the room. So you
|
### Reply generation
|
||||||
shouldn't invite it to a room with other people in it.
|
|
||||||
|
|
||||||
It also supports the `!gptbot help` command, which will print a list of available
|
Note that the bot will respond to _all_ messages in the room by default. If you
|
||||||
commands. Messages starting with `!` are considered commands and will not be
|
don't want this, for example because you want to use the bot in a room with
|
||||||
considered for response generation.
|
other people, you can use the `!gptbot roomsettings` command to change the
|
||||||
|
settings for the current room. For example, you can disable response generation
|
||||||
|
with `!gptbot roomsettings always_reply false`.
|
||||||
|
|
||||||
|
With this setting, the bot will only be triggered if a message begins with
|
||||||
|
`!gptbot chat`. For example, `!gptbot chat Hello, how are you?` will cause the
|
||||||
|
bot to generate a response to the message `Hello, how are you?`. The bot will
|
||||||
|
still get previous messages in the room as context for generating the response.
|
||||||
|
|
||||||
|
### Commands
|
||||||
|
|
||||||
|
There are a few commands that you can use to interact with the bot. For example,
|
||||||
|
if you want to generate an image from a text prompt, you can use the
|
||||||
|
`!gptbot imagine` command. For example, `!gptbot imagine a cat` will cause the
|
||||||
|
bot to generate an image of a cat.
|
||||||
|
|
||||||
|
To learn more about the available commands, `!gptbot help` will print a list of
|
||||||
|
available commands.
|
||||||
|
|
||||||
|
### Automatic classification
|
||||||
|
|
||||||
|
As a beta feature, the bot can automatically classify messages and use the
|
||||||
|
appropriate API to generate a response. For example, if you send a message
|
||||||
|
like "Draw me a picture of a cat", the bot will automatically use the
|
||||||
|
`imagine` command to generate an image of a cat.
|
||||||
|
|
||||||
|
This feature is disabled by default. To enable it, use the `!gptbot roomsettings`
|
||||||
|
command to change the settings for the current room. `!gptbot roomsettings classification true`
|
||||||
|
will enable automatic classification, and `!gptbot roomsettings classification false`
|
||||||
|
will disable it again.
|
||||||
|
|
||||||
|
Note that this feature is still in beta and may not work as expected. You can
|
||||||
|
always use the commands manually if the automatic classification doesn't work
|
||||||
|
for you (including `!gptbot chat` for a regular chat message).
|
||||||
|
|
||||||
|
Also note that this feature conflicts with the `always_reply false` setting -
|
||||||
|
or rather, it doesn't make sense then because you already have to explicitly
|
||||||
|
specify the command to use.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,9 @@ from nio import (
|
||||||
RoomMessageText,
|
RoomMessageText,
|
||||||
RoomSendResponse,
|
RoomSendResponse,
|
||||||
SyncResponse,
|
SyncResponse,
|
||||||
RoomMessageNotice
|
RoomMessageNotice,
|
||||||
|
JoinError,
|
||||||
|
RoomLeaveError,
|
||||||
)
|
)
|
||||||
from nio.crypto import Olm
|
from nio.crypto import Olm
|
||||||
|
|
||||||
|
@ -58,6 +60,7 @@ class GPTBot:
|
||||||
image_api: Optional[OpenAI] = None
|
image_api: Optional[OpenAI] = None
|
||||||
classification_api: Optional[OpenAI] = None
|
classification_api: Optional[OpenAI] = None
|
||||||
operator: Optional[str] = None
|
operator: Optional[str] = None
|
||||||
|
room_ignore_list: List[str] = [] # List of rooms to ignore invites from
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_config(cls, config: ConfigParser):
|
def from_config(cls, config: ConfigParser):
|
||||||
|
@ -276,8 +279,25 @@ class GPTBot:
|
||||||
invites = self.matrix_client.invited_rooms
|
invites = self.matrix_client.invited_rooms
|
||||||
|
|
||||||
for invite in invites.keys():
|
for invite in invites.keys():
|
||||||
|
if invite in self.room_ignore_list:
|
||||||
|
self.logger.log(
|
||||||
|
f"Ignoring invite to room {invite} (room is in ignore list)")
|
||||||
|
continue
|
||||||
|
|
||||||
self.logger.log(f"Accepting invite to room {invite}")
|
self.logger.log(f"Accepting invite to room {invite}")
|
||||||
await self.matrix_client.join(invite)
|
|
||||||
|
response = await self.matrix_client.join(invite)
|
||||||
|
|
||||||
|
if isinstance(response, JoinError):
|
||||||
|
self.logger.log(
|
||||||
|
f"Error joining room {invite}: {response.message}. Not trying again.", "error")
|
||||||
|
|
||||||
|
leave_response = await self.matrix_client.room_leave(invite)
|
||||||
|
|
||||||
|
if isinstance(leave_response, RoomLeaveError):
|
||||||
|
self.logger.log(
|
||||||
|
f"Error leaving room {invite}: {leave_response.message}", "error")
|
||||||
|
self.room_ignore_list.append(invite)
|
||||||
|
|
||||||
async def send_image(self, room: MatrixRoom, image: bytes, message: Optional[str] = None):
|
async def send_image(self, room: MatrixRoom, image: bytes, message: Optional[str] = None):
|
||||||
"""Send an image to a room.
|
"""Send an image to a room.
|
||||||
|
@ -487,13 +507,13 @@ class GPTBot:
|
||||||
await self.matrix_client.sync(timeout=30000)
|
await self.matrix_client.sync(timeout=30000)
|
||||||
|
|
||||||
def respond_to_room_messages(self, room: MatrixRoom | str) -> bool:
|
def respond_to_room_messages(self, room: MatrixRoom | str) -> bool:
|
||||||
"""Check whether the bot should respond to messages sent in a room.
|
"""Check whether the bot should respond to all messages sent in a room.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
room (MatrixRoom | str): The room to check.
|
room (MatrixRoom | str): The room to check.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: Whether the bot should respond to messages sent in the room.
|
bool: Whether the bot should respond to all messages sent in the room.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if isinstance(room, MatrixRoom):
|
if isinstance(room, MatrixRoom):
|
||||||
|
@ -501,7 +521,7 @@ class GPTBot:
|
||||||
|
|
||||||
with self.database.cursor() as cursor:
|
with self.database.cursor() as cursor:
|
||||||
cursor.execute(
|
cursor.execute(
|
||||||
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "respond_to_messages"))
|
"SELECT value FROM room_settings WHERE room_id = ? AND setting = ?", (room, "always_reply"))
|
||||||
result = cursor.fetchone()
|
result = cursor.fetchone()
|
||||||
|
|
||||||
return True if not result else bool(int(result[0]))
|
return True if not result else bool(int(result[0]))
|
||||||
|
|
|
@ -28,4 +28,4 @@ async def command_newroom(room: MatrixRoom, event: RoomMessageText, bot):
|
||||||
|
|
||||||
await bot.matrix_client.joined_rooms()
|
await bot.matrix_client.joined_rooms()
|
||||||
await bot.send_message(room, f"Alright, I've created a new room called '{room_name}' and invited you to it. You can find it at {new_room.room_id}", True)
|
await bot.send_message(room, f"Alright, I've created a new room called '{room_name}' and invited you to it. You can find it at {new_room.room_id}", True)
|
||||||
await bot.send_message(new_room.room_id, f"Welcome to the new room! What can I do for you?")
|
await bot.send_message(new_room, f"Welcome to the new room! What can I do for you?")
|
|
@ -3,10 +3,15 @@ from nio.rooms import MatrixRoom
|
||||||
|
|
||||||
|
|
||||||
async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
|
async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
|
||||||
setting = event.body.split()[2]
|
setting = event.body.split()[2] if len(event.body.split()) > 2 else None
|
||||||
value = " ".join(event.body.split()[3:]) if len(
|
value = " ".join(event.body.split()[3:]) if len(
|
||||||
event.body.split()) > 3 else None
|
event.body.split()) > 3 else None
|
||||||
|
|
||||||
|
if setting == "classification":
|
||||||
|
setting = "use_classification"
|
||||||
|
if setting == "systemmessage":
|
||||||
|
setting = "system_message"
|
||||||
|
|
||||||
if setting == "system_message":
|
if setting == "system_message":
|
||||||
if value:
|
if value:
|
||||||
bot.logger.log("Adding system message...")
|
bot.logger.log("Adding system message...")
|
||||||
|
@ -28,38 +33,52 @@ async def command_roomsettings(room: MatrixRoom, event: RoomMessageText, bot):
|
||||||
await bot.send_message(room, f"The current system message is: '{system_message}'.", True)
|
await bot.send_message(room, f"The current system message is: '{system_message}'.", True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if setting == "classification":
|
if setting in ("use_classification", "always_reply"):
|
||||||
if value:
|
if value:
|
||||||
if value.lower() in ["true", "false"]:
|
if value.lower() in ["true", "false"]:
|
||||||
value = value.lower() == "true"
|
value = value.lower() == "true"
|
||||||
|
|
||||||
bot.logger.log("Setting classification status...")
|
bot.logger.log(f"Setting {setting} status for {room.room_id} to {value}...")
|
||||||
|
|
||||||
with bot.database.cursor() as cur:
|
with bot.database.cursor() as cur:
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"""INSERT INTO room_settings (room_id, setting, value) VALUES (?, ?, ?)
|
"""INSERT INTO room_settings (room_id, setting, value) VALUES (?, ?, ?)
|
||||||
ON CONFLICT (room_id, setting) DO UPDATE SET value = ?;""",
|
ON CONFLICT (room_id, setting) DO UPDATE SET value = ?;""",
|
||||||
(room.room_id, "use_classification", "1" if value else "0", "1" if value else "0")
|
(room.room_id, setting, "1" if value else "0", "1" if value else "0")
|
||||||
)
|
)
|
||||||
|
|
||||||
await bot.send_message(room, f"Alright, I've set use_classification to: '{value}'.", True)
|
await bot.send_message(room, f"Alright, I've set {setting} to: '{value}'.", True)
|
||||||
return
|
return
|
||||||
|
|
||||||
await bot.send_message(room, "You need to provide a boolean value (true/false).", True)
|
await bot.send_message(room, "You need to provide a boolean value (true/false).", True)
|
||||||
return
|
return
|
||||||
|
|
||||||
bot.logger.log("Retrieving classification status...")
|
bot.logger.log(f"Retrieving {setting} status for {room.room_id}...")
|
||||||
|
|
||||||
use_classification = bot.room_uses_classification(room)
|
with bot.database.cursor() as cur:
|
||||||
|
cur.execute(
|
||||||
|
"""SELECT value FROM room_settings WHERE room_id = ? AND setting = ?;""",
|
||||||
|
(room.room_id, setting)
|
||||||
|
)
|
||||||
|
|
||||||
await bot.send_message(room, f"The current classification status is: '{use_classification}'.", True)
|
value = cur.fetchone()[0]
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
if setting == "use_classification":
|
||||||
|
value = False
|
||||||
|
elif setting == "always_reply":
|
||||||
|
value = True
|
||||||
|
else:
|
||||||
|
value = bool(int(value))
|
||||||
|
|
||||||
|
await bot.send_message(room, f"The current {setting} status is: '{value}'.", True)
|
||||||
return
|
return
|
||||||
|
|
||||||
message = f"""
|
message = f"""The following settings are available:
|
||||||
The following settings are available:
|
|
||||||
|
|
||||||
- system_message [message]: Get or set the system message to be sent to the chat model
|
- system_message [message]: Get or set the system message to be sent to the chat model
|
||||||
- classification [true/false]: Get or set whether the room uses classification
|
- classification [true/false]: Get or set whether the room uses classification
|
||||||
"""
|
- always_reply [true/false]: Get or set whether the bot should reply to all messages (if false, only reply to mentions and commands)
|
||||||
|
"""
|
||||||
|
|
||||||
await bot.send_message(room, message, True)
|
await bot.send_message(room, message, True)
|
||||||
|
|
Loading…
Reference in a new issue