Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allowing user to choose presets. #183

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open

Conversation

hereAlexT
Copy link

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!

@n3d1117
Copy link
Owner

n3d1117 commented Apr 6, 2023

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?

@hereAlexT
Copy link
Author

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!

@n3d1117
Copy link
Owner

n3d1117 commented Apr 6, 2023

Sure, is there a limit on the number of inline buttons that can be shown?

@hereAlexT
Copy link
Author

hereAlexT commented Apr 6, 2023

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."

@AlexHTW
Copy link
Contributor

AlexHTW commented Apr 6, 2023

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.

@hereAlexT
Copy link
Author

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.

@AlexHTW
Copy link
Contributor

AlexHTW commented Apr 7, 2023

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 the reset-function in telegrambot.py. Checking if content starts with the special character and then applying your config logic and calling reset_chat_history.

@hereAlexT
Copy link
Author

@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().

@AlexHTW
Copy link
Contributor

AlexHTW commented Apr 8, 2023

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.

@alparo
Copy link

alparo commented May 13, 2023

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:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants