chatgpt-telegram-bot
chatgpt-telegram-bot copied to clipboard
Allowing user to choose presets.
Changes
- Added functionality for users to set presets.
- Introduced a new file called config.json where users can define their preferred presets.
- Implemented the command
/mode [mode_name]
to allow users to switch between presets. - The default preset can be set in the .env file.
- When changing to a new preset, the user's custom settings will override the default preset settings.
- When switching from one preset to another, the settings will be reset to the default preset before applying the new one.
These changes will allow users to easily switch between different preset configurations.
Thank you!
Hi @ShaomingT, thanks this looks great! I was wondering if there's a nicer way UX-wise to let the user select a preset from the list? Maybe using inline buttons?
Hi @ShaomingT, thanks this looks great! I was wondering if there's a nicer way UX-wise to let the user select a preset from the list? Maybe using inline buttons?
Hi @n3d1117 ! Thanks for the idea!
A potential issue is that if the user sets many modes (e.g. nine modes), it could be hard to use with inline buttons.
Please let me know your thoughts on this. I'm happy to discuss this!
Sure, is there a limit on the number of inline buttons that can be shown?
It looks like the doc didn't mention the limit number of inline buttons.
If there are many rows and columns showing buttons, and considering the length of the text on the buttons, it can make the interface more difficult to recognize.
One solution is to only display the first few modes (e.g. 4 modes) as inline buttons. If there are more options, the /mode mode_name
command can be used to select them."
Hey, how about a scrollable list of buttons, if there are too many. Navigating with a next
and previous
button.
Here is a snippet GPT-4 gave me for this suggestion (I have no clue about buttons):
import math
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import CommandHandler, CallbackQueryHandler
# Your list of items
items = ['Item 1', 'Item 2', 'Item 3', 'Item 4', 'Item 5', 'Item 6', 'Item 7', 'Item 8', 'Item 9', 'Item 10']
ITEMS_PER_PAGE = 5
def build_keyboard(page):
n_pages = math.ceil(len(items) / ITEMS_PER_PAGE)
start = page * ITEMS_PER_PAGE
end = start + ITEMS_PER_PAGE
keyboard = []
for i, item in enumerate(items[start:end], start=start):
keyboard.append([InlineKeyboardButton(f"{i+1}. {item}", callback_data=f"item-{i}")])
# Navigation buttons
navigation_buttons = []
if page > 0:
navigation_buttons.append(InlineKeyboardButton("Previous", callback_data=f"previous-{page}"))
if page < n_pages - 1:
navigation_buttons.append(InlineKeyboardButton("Next", callback_data=f"next-{page}"))
keyboard.append(navigation_buttons)
return InlineKeyboardMarkup(keyboard)
def list_items(update, context):
message = "Choose an item:"
keyboard = build_keyboard(0)
update.message.reply_text(message, reply_markup=keyboard)
def handle_button_click(update, context):
query = update.callback_query
data = query.data.split('-')
action = data[0]
index = int(data[1])
if action == "item":
query.answer(f"You selected item {index+1}: {items[index]}")
else:
if action == "next":
new_page = index + 1
elif action == "previous":
new_page = index - 1
keyboard = build_keyboard(new_page)
query.edit_message_reply_markup(reply_markup=keyboard)
def main():
updater = Updater(TOKEN, use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler("list_items", list_items))
dp.add_handler(CallbackQueryHandler(handle_button_click))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
In this example, the /list_items command displays a list of items divided into pages with 5 items per page. The user can navigate through the pages using "Next" and "Previous" buttons. When the user clicks on an item, the bot will respond with a message indicating the selected item.
Remember to replace TOKEN with your bot token and customize the items list and ITEMS_PER_PAGE constant as needed.
I think we should have two methods for different use cases:
- /mode mode_name
- Inline buttons
The first one is for people who have lots of modes and know what they want. It's more like a programmer's way.
The second one is more general and user-friendly if they have fewer modes. I think scrolling through a bunch of pages to find the one that the user wants may not be a good user experience.
Alright, fair enough, it's personal preference and your suggestion is a good compromise. The button part could be added in a later update. I tested your PR a bit and it's a very convenient feature, saving a lot of typing. However, I discovered an issue - when user A sets a mode and user B resets the conversation, user B gets the mode of user A. I understand your goal to have the mode be persistent between resets but you will probably need to remember the config per user. A good solution to this would be great because it could include other user settings like model in future enhancements. BTW you are not using the temperature setting from your config.json.example anywhere, are you?
An alternative would be not having the mode persist between resets. Then you could add the first method you proposed to the /reset
command where a mode is initiated by a special character, for example #
.
So that for example:
-
/reset
gives you the default initial prompt -
/reset you are a pirate
sets a custom initial prompt -
/reset #code
sets the initial prompt of the mode "code" For this, the mode stuff could just be integrated into thereset
-function intelegrambot.py
. Checking if content starts with the special character and then applying your config logic and callingreset_chat_history
.
@AlexHTW
Actually, it is able to rewrite all the settings in the var main.py -> openai_config
openai_config = {
'api_key': os.environ['OPENAI_API_KEY'],
'show_usage': os.environ.get('SHOW_USAGE', 'false').lower() == 'true',
'stream': os.environ.get('STREAM', 'true').lower() == 'true',
'proxy': os.environ.get('PROXY', None),
'max_history_size': int(os.environ.get('MAX_HISTORY_SIZE', 15)),
'max_conversation_age_minutes': int(os.environ.get('MAX_CONVERSATION_AGE_MINUTES', 180)),
'assistant_prompt': os.environ.get('ASSISTANT_PROMPT', 'You are a helpful assistant.'),
'max_tokens': int(os.environ.get('MAX_TOKENS', max_tokens_default)),
'n_choices': int(os.environ.get('N_CHOICES', 1)),
'temperature': float(os.environ.get('TEMPERATURE', 1.0)),
'image_size': os.environ.get('IMAGE_SIZE', '512x512'),
'model': model,
'presence_penalty': float(os.environ.get('PRESENCE_PENALTY', 0.0)),
'frequency_penalty': float(os.environ.get('FREQUENCY_PENALTY', 0.0)),
}
You can compare the temperature between 0.1 and 1.9. The result will be obvious.
The logic is:
The app starts with curr_settings = default settings = settings in .env file
When user change from mode A to mode B, it will do the following
curr_settings = default settings # first reset the settings to default
curr_settings = mode b # set the settings to mode B
I'll investigate the bug "not remember config per user"
Thanks for your suggestion; now I understand the goal of the project.
Consider the following solution ->
Case 1
User input: /mode
Output:
[some text: Choose one mode from the following buttons]
---
[btn: mode 1][btn: mode 2][btn: mode 3][btn: mode 4]
[btn: mode 5][btn: mode 6][btn: mode 7][btn: mode 8]
Case 2 User input '/mode [mode_name]` Output:
[text: you have changed to [mode_name]]
In this way, when we do the inline button enhancement in the future, we only need to modify and add some codes in the method mode()
.
Hey @ShaomingT,
Actually, it is able to rewrite all the settings in the var main.py -> openai_config
That's great, some of them would be really nice to customize through a command per server. Some would be really great per user, such as model, image_size and of course mode.
What do you think about extending the usage_tracker class (and the usage_logs json files) with a settings
dict. For starters only with the setting for mode.
Then, for the initialization you could check if there is a mode saved, if not use the default and if a user changes the mode overwrite it in the usage
object and dump to the JSON.
That way it will also persist between server restarts. In the future a user_logs JSON could look like this:
{
"user_name": "@user_name",
"current_cost": {
"day": 0.45,
"month": 3.23,
"last_update": "2023-03-14"
},
"usage_history": {
"chat_tokens": {
"2023-03-13": 520,
"2023-03-14": 1532
},
"transcription_seconds": {
"2023-03-13": 125,
"2023-03-14": 64
},
"number_images": {
"2023-03-12": [
0,
2,
3
]
}
},
"settings": {
"mode": "code",
"model": "gpt-4",
"image_size": "512x512"
}
}
In this way, when we do the inline button enhancement in the future, we only need to modify and add some codes in the method mode().
You are right, it is much cleaner that way. My suggestion was only a temporary solution.
I love the idea of mods! If I understand correctly the user can create a mod with preloaded information+context and then ask the bot in this mode to use provided information in its replies. I'm not a very experienced developer but I'd like to throw in my two cents on the topic how to display a list of all created mods. We can limit mode name with one word, maybe allow underscores. Then the bot can return the list of modes as the command list. Like: /mode_default /mode_philosopher /mode_expenditure Then the user can just click on the command and it will be automatically sent to the chat. Such a list can contain a lot of mods with their description.
E.g. I came up with the following format for one telegram game where all the commands in blue are generated on the fly while reading the names of items from DB: