django-unicom 25.2.31.dev0__tar.gz → 25.2.32.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/PKG-INFO +341 -133
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/README.md +340 -132
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/PKG-INFO +341 -133
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/SOURCES.txt +6 -0
- django_unicom-25.2.32.dev0/send_test_buttons.py +54 -0
- django_unicom-25.2.32.dev0/test_comprehensive_buttons.py +323 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/models.py +6 -3
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/llm_handler.py +27 -13
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/_version.py +2 -2
- django_unicom-25.2.32.dev0/unicom/migrations/0014_rename_authorized_user_to_intended_account.py +18 -0
- django_unicom-25.2.32.dev0/unicom/migrations/0015_simplify_callback_execution.py +58 -0
- django_unicom-25.2.32.dev0/unicom/migrations/0016_callbackexecution_tool_call.py +19 -0
- django_unicom-25.2.32.dev0/unicom/models/callback_execution.py +37 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/create_inline_keyboard.py +36 -3
- django_unicom-25.2.32.dev0/unicom/services/telegram/handle_telegram_callback.py +103 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/signals.py +2 -2
- django_unicom-25.2.32.dev0/unicom_project/callback_handlers.py +174 -0
- django_unicom-25.2.32.dev0/unicom_project/definitions/tools/interactive_menu.py +91 -0
- django_unicom-25.2.32.dev0/unicom_project/test_button_handlers.py +165 -0
- django_unicom-25.2.31.dev0/unicom/models/callback_execution.py +0 -39
- django_unicom-25.2.31.dev0/unicom/services/telegram/handle_telegram_callback.py +0 -196
- django_unicom-25.2.31.dev0/unicom_project/callback_handlers.py +0 -222
- django_unicom-25.2.31.dev0/unicom_project/definitions/tools/interactive_menu.py +0 -104
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/match_codebase_style.mdc +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/preserve_comments_and_irrelevant_code.mdc +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/.cursor/rules/require_context_before_changes.mdc +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/Dockerfile +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/MANIFEST.in +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/Makefile +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/conftest.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/data/definitions/bots/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/data/definitions/tools/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/dependency_links.txt +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/requires.txt +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/django_unicom.egg-info/top_level.txt +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/docker-compose.yaml +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/entrypoint.sh +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/manage.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/pyproject.toml +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/pytest.ini +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/requirements.txt +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/setup.cfg +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/setup.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/test_dkim_security.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_authentication.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_live.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_email_request_processing.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/test_telegram_live.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/tests/utils.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/apps.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0001_initial.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0002_tool_bot_tools.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0003_encryptedcredential.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0004_credentialfielddefinition_and_more.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/0005_alter_credentialfielddefinition_key.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/migrations/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/credentials.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/services/tool_result_handler.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/tests.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/urls.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unibot/views.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/account_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/channel_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/chat_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/draft_message_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/email_inline_image_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/filters.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/member_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/message_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/message_template_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/admin/request_admin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/apps.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/mail_inbox_listen.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/run_as_llm_chat.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/send_scheduled_messages.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/management/commands/start_imap_listeners.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0001_initial.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0002_emailinlineimage.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0003_alter_emailinlineimage_email_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0004_messagetemplateinlineimage.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0005_emailinlineimage_hash_and_more.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0006_channel_created_at_channel_created_by_and_more.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0007_message_imap_uid.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0008_add_all_members_group.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0009_add_tool_call_types.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0010_request_initial_request_request_llm_calls_count_and_more.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0011_toolcall_add_message_reference.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0012_message_response_to_tool_call_and_more.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/0013_callbackexecution.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/migrations/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/account.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/account_chat.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/channel.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/chat.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/constants.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/draft_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/fields.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/member.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/member_group.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/message_template.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/request.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/request_category.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/tool_call.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/models/update.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/chat_summary.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/reply_to_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/scheduler.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/crossplatform/send_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/decode_base64_image.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/IMAP_thread_manager.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/email_tracking.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/listen_to_IMAP.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/quote_filter.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/replace_cid_images_with_base64.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/save_email_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/send_email_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/email/validate_email_config.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/get_public_origin.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/html_inline_images.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/generate_text_message_data.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/save_internal_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/internal/send_internal_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/README.md +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/llm/tool_calls.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/answer_callback_query.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/download_file.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/edit_telegram_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/escape_markdown.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/get_file_path.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/save_telegram_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/send_telegram_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/set_telegram_webhook.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/start_typing_in_telegram.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/telegram/stop_typing_in_telegram.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/get_template.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/save_whatsapp_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/save_whatsapp_message_status.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/services/whatsapp/send_whatsapp_message.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/css/bootstrap_scoped.css +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/css/draft_message_mobile.css +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/channel_config.js +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/tinymce_ai_template.js +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/static/unicom/js/tinymce_init.js +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat/change_list.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat/compose.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/chat_history.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/forms/email_message_form.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/forms/text_message_form.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/ai_template_modal.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/loading_indicators.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/includes/message_actions_menu.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/admin/unicom/messagetemplate/change_form.html +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/templates/code_templates/category_processor.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/urls.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/chat_history_view.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/compose_view.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/email_tracking.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/inline_image.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/message_template.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/telegram_webhook.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom/views/whatsapp_webhook.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/apps.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/asgi.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/bots/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/bots/assistant_bot.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/__init__.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/interval_alarm.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/ip_lookup.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/simple_timer.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/definitions/tools/system_info.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/settings.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/urls.py +0 -0
- {django_unicom-25.2.31.dev0 → django_unicom-25.2.32.dev0}/unicom_project/wsgi.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-unicom
|
|
3
|
-
Version: 25.2.
|
|
3
|
+
Version: 25.2.32.dev0
|
|
4
4
|
Summary: Unified communication layer for Django (Telegram, WhatsApp, Email)
|
|
5
5
|
Home-page: https://github.com/meena-erian/unicom
|
|
6
6
|
Author: Meena (Menas) Erian
|
|
@@ -61,6 +61,9 @@ Dynamic: summary
|
|
|
61
61
|
- [Telegram-Specific Features](#telegram-specific-features)
|
|
62
62
|
- [Typing Indicators](#-typing-indicators)
|
|
63
63
|
- [Interactive Buttons & Callbacks](#-interactive-messages-with-action-buttons)
|
|
64
|
+
- [Handling Button Clicks](#-handling-button-clicks)
|
|
65
|
+
- [Tool-Generated Buttons](#-tool-generated-buttons-advanced)
|
|
66
|
+
- [Button Routing Best Practices](#-button-routing-best-practices)
|
|
64
67
|
- [Editing Messages](#-editing-telegram-messages)
|
|
65
68
|
- [File Downloads and Voice Messages](#-file-downloads-and-voice-messages)
|
|
66
69
|
- [LLM Integration](#llm-integration)
|
|
@@ -287,17 +290,19 @@ message = email_channel.send_message({
|
|
|
287
290
|
})
|
|
288
291
|
|
|
289
292
|
# 📱 Telegram only: Message with interactive buttons
|
|
290
|
-
from unicom.services.telegram.create_inline_keyboard import
|
|
293
|
+
from unicom.services.telegram.create_inline_keyboard import create_callback_button, create_inline_keyboard
|
|
291
294
|
|
|
295
|
+
# For initial messages, use legacy mode (no message parameter)
|
|
296
|
+
# Buttons will work but won't be linked to CallbackExecution
|
|
292
297
|
message = telegram_channel.send_message({
|
|
293
298
|
'chat_id': 'user_chat_id',
|
|
294
299
|
'text': 'Choose an option:',
|
|
295
|
-
'reply_markup':
|
|
296
|
-
"Confirm", "
|
|
297
|
-
"Cancel", "
|
|
298
|
-
)
|
|
300
|
+
'reply_markup': create_inline_keyboard([
|
|
301
|
+
[create_callback_button("Confirm", {"action": "confirm"})],
|
|
302
|
+
[create_callback_button("Cancel", {"action": "cancel"})]
|
|
303
|
+
])
|
|
299
304
|
})
|
|
300
|
-
# See "Interactive Buttons & Callbacks" section for
|
|
305
|
+
# See "Interactive Buttons & Callbacks" section for full details
|
|
301
306
|
```
|
|
302
307
|
|
|
303
308
|
### Message Model
|
|
@@ -441,14 +446,14 @@ reply = message.reply_with({
|
|
|
441
446
|
})
|
|
442
447
|
|
|
443
448
|
# 📱 Telegram only: Reply with interactive buttons
|
|
444
|
-
from unicom.services.telegram.create_inline_keyboard import
|
|
449
|
+
from unicom.services.telegram.create_inline_keyboard import create_inline_keyboard, create_callback_button
|
|
445
450
|
|
|
446
451
|
reply = message.reply_with({
|
|
447
452
|
'text': 'Would you like to continue?',
|
|
448
|
-
'reply_markup':
|
|
449
|
-
"Yes", "
|
|
450
|
-
"No", "
|
|
451
|
-
)
|
|
453
|
+
'reply_markup': create_inline_keyboard([
|
|
454
|
+
[create_callback_button("Yes", {"action": "continue", "value": True}, message=message)],
|
|
455
|
+
[create_callback_button("No", {"action": "continue", "value": False}, message=message)]
|
|
456
|
+
])
|
|
452
457
|
})
|
|
453
458
|
# See "Interactive Buttons & Callbacks" section for handling button clicks
|
|
454
459
|
```
|
|
@@ -660,39 +665,46 @@ message = telegram_channel.send_message({
|
|
|
660
665
|
|
|
661
666
|
#### 📱 Interactive Messages with Action Buttons
|
|
662
667
|
|
|
663
|
-
Telegram channels support inline keyboard buttons
|
|
668
|
+
Telegram channels support inline keyboard buttons. Pass any JSON-serializable `callback_data`:
|
|
664
669
|
|
|
665
670
|
```python
|
|
666
671
|
from unicom.services.telegram.create_inline_keyboard import (
|
|
667
|
-
create_inline_keyboard, create_callback_button, create_url_button
|
|
672
|
+
create_inline_keyboard, create_callback_button, create_url_button
|
|
668
673
|
)
|
|
669
674
|
|
|
670
|
-
#
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
[create_url_button("Visit Website", "https://example.com")]
|
|
674
|
-
])
|
|
675
|
-
|
|
676
|
-
message = telegram_channel.send_message({
|
|
677
|
-
'chat_id': 'telegram_chat_id',
|
|
675
|
+
# When replying to existing messages, pass message and account for full features
|
|
676
|
+
# This creates CallbackExecution records and enables security + tool integration
|
|
677
|
+
incoming_message.reply_with({
|
|
678
678
|
'text': 'Do you want to continue?',
|
|
679
|
-
'reply_markup':
|
|
679
|
+
'reply_markup': create_inline_keyboard([
|
|
680
|
+
[create_callback_button("Yes", {"action": "confirm"}, message=incoming_message)],
|
|
681
|
+
[create_callback_button("No", {"action": "cancel"}, message=incoming_message)],
|
|
682
|
+
[create_url_button("Visit Website", "https://example.com")]
|
|
683
|
+
])
|
|
680
684
|
})
|
|
681
685
|
|
|
682
|
-
#
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
'
|
|
686
|
-
|
|
687
|
-
|
|
686
|
+
# Rich data structures with any JSON-serializable data
|
|
687
|
+
incoming_message.reply_with({
|
|
688
|
+
'text': 'Choose a product:',
|
|
689
|
+
'reply_markup': create_inline_keyboard([
|
|
690
|
+
[create_callback_button("Product A", {"product_id": 123, "price": 29.99, "stock": 5}, message=incoming_message)],
|
|
691
|
+
[create_callback_button("Product B", {"product_id": 456, "price": 49.99, "stock": 12}, message=incoming_message)]
|
|
692
|
+
])
|
|
688
693
|
})
|
|
689
694
|
|
|
690
|
-
#
|
|
691
|
-
|
|
692
|
-
|
|
695
|
+
# Optional expiration for time-limited offers
|
|
696
|
+
from django.utils import timezone
|
|
697
|
+
from datetime import timedelta
|
|
698
|
+
|
|
699
|
+
incoming_message.reply_with({
|
|
700
|
+
'text': 'Limited time offer!',
|
|
693
701
|
'reply_markup': create_inline_keyboard([
|
|
694
|
-
[create_callback_button(
|
|
695
|
-
|
|
702
|
+
[create_callback_button(
|
|
703
|
+
"Claim Offer",
|
|
704
|
+
{"offer_id": 789, "discount": 0.5},
|
|
705
|
+
message=incoming_message,
|
|
706
|
+
expires_at=timezone.now() + timedelta(hours=24)
|
|
707
|
+
)]
|
|
696
708
|
])
|
|
697
709
|
})
|
|
698
710
|
```
|
|
@@ -706,62 +718,53 @@ from django.dispatch import receiver
|
|
|
706
718
|
from unicom.signals import telegram_callback_received
|
|
707
719
|
|
|
708
720
|
@receiver(telegram_callback_received)
|
|
709
|
-
def handle_button_clicks(sender, callback_execution, **kwargs):
|
|
721
|
+
def handle_button_clicks(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
|
|
710
722
|
"""
|
|
711
|
-
Handle
|
|
712
|
-
Each callback is processed exactly once across all Django processes.
|
|
713
|
-
|
|
714
|
-
Available data in callback_execution:
|
|
715
|
-
- callback_data: The button's callback_data string
|
|
716
|
-
- authorized_user: The validated user (Account object)
|
|
717
|
-
- callback_message: Message object for sending responses
|
|
718
|
-
- original_message: The message that contained the buttons
|
|
719
|
-
"""
|
|
720
|
-
# Get the button data and validated user
|
|
721
|
-
button_data = callback_execution.callback_data # e.g., "confirm_action"
|
|
722
|
-
user = callback_execution.authorized_user # Security-validated user
|
|
723
|
-
callback_msg = callback_execution.callback_message # For replying
|
|
724
|
-
original_msg = callback_execution.original_message # Original message with buttons
|
|
725
|
-
|
|
726
|
-
if button_data == 'confirm_action':
|
|
727
|
-
# Process confirmation
|
|
728
|
-
process_user_confirmation(user)
|
|
729
|
-
callback_msg.reply_with({'text': '✅ Confirmed!'})
|
|
730
|
-
|
|
731
|
-
elif button_data == 'cancel_action':
|
|
732
|
-
# Process cancellation
|
|
733
|
-
callback_msg.reply_with({'text': '❌ Cancelled'})
|
|
734
|
-
|
|
735
|
-
elif button_data.startswith('product_'):
|
|
736
|
-
# Handle dynamic data (e.g., "product_123")
|
|
737
|
-
product_id = button_data.split('_')[1]
|
|
738
|
-
product = get_product(product_id)
|
|
739
|
-
|
|
740
|
-
# Send response with new buttons
|
|
741
|
-
callback_msg.reply_with({
|
|
742
|
-
'text': f'Product: {product.name}\nPrice: ${product.price}',
|
|
743
|
-
'reply_markup': create_simple_keyboard(
|
|
744
|
-
'Buy Now', f'buy_{product_id}',
|
|
745
|
-
'Add to Cart', f'cart_{product_id}'
|
|
746
|
-
)
|
|
747
|
-
})
|
|
723
|
+
Handle button clicks.
|
|
748
724
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
'reply_markup': create_inline_keyboard([
|
|
755
|
-
[create_callback_button("Option 1", "opt1")],
|
|
756
|
-
[create_callback_button("Option 2", "opt2")]
|
|
757
|
-
])
|
|
758
|
-
})
|
|
725
|
+
Args:
|
|
726
|
+
callback_execution: CallbackExecution instance with callback_data
|
|
727
|
+
clicking_account: The unicom.Account that clicked the button
|
|
728
|
+
original_message: The Message containing the buttons
|
|
729
|
+
tool_call: Optional ToolCall if button was from a tool (None otherwise)
|
|
759
730
|
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
731
|
+
Note: unicom.Account represents a platform user (e.g., Telegram user, email address).
|
|
732
|
+
To access Django auth.User: clicking_account.member.user (if member exists)
|
|
733
|
+
"""
|
|
734
|
+
data = callback_execution.callback_data
|
|
735
|
+
|
|
736
|
+
# Handle dict callback_data
|
|
737
|
+
if isinstance(data, dict):
|
|
738
|
+
if data.get('action') == 'confirm':
|
|
739
|
+
process_confirmation(clicking_account)
|
|
740
|
+
original_message.reply_with({'text': '✅ Confirmed!'})
|
|
741
|
+
|
|
742
|
+
elif data.get('action') == 'buy_product':
|
|
743
|
+
product_id = data['product_id']
|
|
744
|
+
product = get_product(product_id)
|
|
745
|
+
|
|
746
|
+
# Create new buttons with callback_data
|
|
747
|
+
original_message.reply_with({
|
|
748
|
+
'text': f'Product: {product.name}\nPrice: ${product.price}',
|
|
749
|
+
'reply_markup': create_inline_keyboard([
|
|
750
|
+
[create_callback_button('Confirm Purchase', {'action': 'confirm_purchase', 'product_id': product_id}, message=original_message, account=clicking_account)],
|
|
751
|
+
[create_callback_button('Cancel', {'action': 'cancel'}, message=original_message, account=clicking_account)]
|
|
752
|
+
])
|
|
753
|
+
})
|
|
754
|
+
|
|
755
|
+
# Handle string callback_data
|
|
756
|
+
elif data == 'cancel':
|
|
757
|
+
original_message.reply_with({'text': '❌ Cancelled'})
|
|
758
|
+
|
|
759
|
+
# Access Django User if needed
|
|
760
|
+
if clicking_account.member and clicking_account.member.user:
|
|
761
|
+
django_user = clicking_account.member.user
|
|
762
|
+
# Do something with django_user
|
|
763
|
+
|
|
764
|
+
# If button was from a tool, you can respond to the tool call
|
|
765
|
+
if tool_call and data.get('action') == 'confirm':
|
|
766
|
+
# Inform the LLM that the user confirmed
|
|
767
|
+
tool_call.respond({'confirmed': True, 'user_id': clicking_account.id})
|
|
765
768
|
```
|
|
766
769
|
|
|
767
770
|
**Where to put your callback handler:**
|
|
@@ -781,24 +784,188 @@ class YourAppConfig(AppConfig):
|
|
|
781
784
|
|
|
782
785
|
Make sure your app is in `INSTALLED_APPS` in settings.py.
|
|
783
786
|
|
|
784
|
-
**
|
|
787
|
+
**Key Features:**
|
|
788
|
+
|
|
789
|
+
- **Flexible Data**: Store any JSON-serializable data (dict, list, str, int, bool, None)
|
|
790
|
+
- **Security**: Only the intended account can click the button
|
|
791
|
+
- **Expiration**: Optional `expires_at` parameter for time-limited buttons
|
|
792
|
+
- **Reusable**: Buttons can be clicked multiple times (developers control behavior)
|
|
793
|
+
- **Efficient**: Callback data stored in DB, only ID sent to Telegram
|
|
794
|
+
- **No Message Creation**: Button clicks don't create Message objects - they only trigger handlers
|
|
795
|
+
- **Tool Integration**: When buttons are from tools, handlers can use `tool_call.respond()` to inform the LLM
|
|
796
|
+
|
|
797
|
+
#### 📱 Tool-Generated Buttons (Advanced)
|
|
798
|
+
|
|
799
|
+
When a tool sends buttons, you can link them to the ToolCall so handlers can respond to the LLM:
|
|
800
|
+
|
|
801
|
+
```python
|
|
802
|
+
# In your tool code (e.g., unibot Tool model)
|
|
803
|
+
def my_interactive_tool(question: str) -> str:
|
|
804
|
+
"""Ask user a question with buttons and wait for response."""
|
|
805
|
+
|
|
806
|
+
# tool_call is available in tool context - no need to query!
|
|
807
|
+
# Just use it directly
|
|
808
|
+
|
|
809
|
+
# Send message with buttons linked to this tool call
|
|
810
|
+
message.reply_with({
|
|
811
|
+
'text': f'Question: {question}',
|
|
812
|
+
'reply_markup': create_inline_keyboard([
|
|
813
|
+
[create_callback_button(
|
|
814
|
+
"Yes",
|
|
815
|
+
{"tool": "my_interactive_tool", "action": "answer", "value": "yes"},
|
|
816
|
+
message=message,
|
|
817
|
+
tool_call=tool_call # Use the context variable
|
|
818
|
+
)],
|
|
819
|
+
[create_callback_button(
|
|
820
|
+
"No",
|
|
821
|
+
{"tool": "my_interactive_tool", "action": "answer", "value": "no"},
|
|
822
|
+
message=message,
|
|
823
|
+
tool_call=tool_call
|
|
824
|
+
)]
|
|
825
|
+
])
|
|
826
|
+
})
|
|
827
|
+
|
|
828
|
+
# Return None to defer response - tool will respond when user clicks
|
|
829
|
+
return None
|
|
830
|
+
|
|
831
|
+
# In your callback handler (callback_handlers.py)
|
|
832
|
+
@receiver(telegram_callback_received)
|
|
833
|
+
def handle_tool_buttons(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
|
|
834
|
+
data = callback_execution.callback_data
|
|
835
|
+
|
|
836
|
+
# Route to correct handler based on tool name
|
|
837
|
+
if isinstance(data, dict) and data.get('tool') == 'my_interactive_tool':
|
|
838
|
+
if data.get('action') == 'answer':
|
|
839
|
+
# User clicked a button from the tool
|
|
840
|
+
answer = data['value']
|
|
841
|
+
|
|
842
|
+
# Respond to the tool call - this will notify the LLM
|
|
843
|
+
if tool_call:
|
|
844
|
+
tool_call.respond({
|
|
845
|
+
'question_answered': True,
|
|
846
|
+
'answer': answer,
|
|
847
|
+
'user_id': clicking_account.id
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
# Also send confirmation to user
|
|
851
|
+
original_message.reply_with({
|
|
852
|
+
'text': f'✅ You answered: {answer}'
|
|
853
|
+
})
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
#### 📱 Button Routing Best Practices
|
|
857
|
+
|
|
858
|
+
When building applications with multiple button types, use a consistent routing strategy:
|
|
859
|
+
|
|
860
|
+
**Recommended Pattern: Use a "type" or "handler" field**
|
|
861
|
+
|
|
862
|
+
```python
|
|
863
|
+
# Define button types as constants for consistency
|
|
864
|
+
BUTTON_TYPES = {
|
|
865
|
+
'PRODUCT': 'product_handler',
|
|
866
|
+
'NAVIGATION': 'nav_handler',
|
|
867
|
+
'SETTINGS': 'settings_handler',
|
|
868
|
+
'TOOL': 'tool_handler'
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
# Create buttons with type field
|
|
872
|
+
create_callback_button(
|
|
873
|
+
"Buy Product A",
|
|
874
|
+
{
|
|
875
|
+
"type": "product_handler", # Routes to product handler
|
|
876
|
+
"action": "buy",
|
|
877
|
+
"product_id": 123
|
|
878
|
+
},
|
|
879
|
+
message=message
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
create_callback_button(
|
|
883
|
+
"Settings",
|
|
884
|
+
{
|
|
885
|
+
"type": "settings_handler", # Routes to settings handler
|
|
886
|
+
"action": "show_settings"
|
|
887
|
+
},
|
|
888
|
+
message=message
|
|
889
|
+
)
|
|
890
|
+
|
|
891
|
+
# In your callback handler - route based on type
|
|
892
|
+
@receiver(telegram_callback_received)
|
|
893
|
+
def handle_all_buttons(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
|
|
894
|
+
data = callback_execution.callback_data
|
|
895
|
+
|
|
896
|
+
if not isinstance(data, dict):
|
|
897
|
+
return # Skip non-dict data
|
|
898
|
+
|
|
899
|
+
# Route to appropriate handler based on type
|
|
900
|
+
handler_type = data.get('type')
|
|
901
|
+
|
|
902
|
+
if handler_type == 'product_handler':
|
|
903
|
+
handle_product_buttons(data, clicking_account, original_message)
|
|
904
|
+
elif handler_type == 'settings_handler':
|
|
905
|
+
handle_settings_buttons(data, clicking_account, original_message)
|
|
906
|
+
elif handler_type == 'tool_handler':
|
|
907
|
+
handle_tool_buttons(data, clicking_account, original_message, tool_call)
|
|
908
|
+
else:
|
|
909
|
+
# Unknown type - log or handle gracefully
|
|
910
|
+
print(f"Unknown button type: {handler_type}")
|
|
911
|
+
|
|
912
|
+
def handle_product_buttons(data, account, message):
|
|
913
|
+
"""Handle product-related button clicks"""
|
|
914
|
+
if data.get('action') == 'buy':
|
|
915
|
+
product_id = data['product_id']
|
|
916
|
+
# Process purchase...
|
|
917
|
+
message.reply_with({'text': f'Processing purchase for product {product_id}'})
|
|
918
|
+
|
|
919
|
+
def handle_settings_buttons(data, account, message):
|
|
920
|
+
"""Handle settings-related button clicks"""
|
|
921
|
+
if data.get('action') == 'show_settings':
|
|
922
|
+
# Show settings menu...
|
|
923
|
+
message.edit_original_message({'text': '⚙️ Settings Menu'})
|
|
924
|
+
|
|
925
|
+
def handle_tool_buttons(data, account, message, tool_call):
|
|
926
|
+
"""Handle tool-generated button clicks"""
|
|
927
|
+
if tool_call and data.get('action') == 'confirm':
|
|
928
|
+
tool_call.respond({'confirmed': True})
|
|
929
|
+
message.reply_with({'text': '✅ Confirmed'})
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
**Alternative Pattern: Multiple Signal Receivers**
|
|
785
933
|
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
934
|
+
```python
|
|
935
|
+
# Register separate handlers for different button types
|
|
936
|
+
@receiver(telegram_callback_received)
|
|
937
|
+
def handle_product_buttons(sender, callback_execution, **kwargs):
|
|
938
|
+
data = callback_execution.callback_data
|
|
939
|
+
# Only handle product buttons
|
|
940
|
+
if isinstance(data, dict) and data.get('type') == 'product':
|
|
941
|
+
# Handle product actions...
|
|
942
|
+
pass
|
|
790
943
|
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
944
|
+
@receiver(telegram_callback_received)
|
|
945
|
+
def handle_navigation_buttons(sender, callback_execution, **kwargs):
|
|
946
|
+
data = callback_execution.callback_data
|
|
947
|
+
# Only handle navigation buttons
|
|
948
|
+
if isinstance(data, dict) and data.get('type') == 'nav':
|
|
949
|
+
# Handle navigation...
|
|
950
|
+
pass
|
|
951
|
+
```
|
|
795
952
|
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
953
|
+
**Scalable Data Structure Example:**
|
|
954
|
+
|
|
955
|
+
```python
|
|
956
|
+
# Well-structured callback data for complex applications
|
|
957
|
+
callback_data = {
|
|
958
|
+
"type": "product_handler", # Routes to correct handler
|
|
959
|
+
"action": "add_to_cart", # Specific action
|
|
960
|
+
"entity_type": "product", # Type of entity
|
|
961
|
+
"entity_id": 123, # Entity identifier
|
|
962
|
+
"metadata": { # Additional context
|
|
963
|
+
"source": "search_results",
|
|
964
|
+
"page": 2
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
```
|
|
800
968
|
|
|
801
|
-
**For more examples and best practices**, see the [detailed Telegram buttons guide](docs/telegram_buttons_guide.md).
|
|
802
969
|
|
|
803
970
|
#### 📱 Editing Telegram Messages
|
|
804
971
|
|
|
@@ -818,24 +985,22 @@ edit_telegram_message(telegram_channel, message, {
|
|
|
818
985
|
})
|
|
819
986
|
|
|
820
987
|
# Edit messages with buttons (common in callback handlers)
|
|
821
|
-
# Use the edit_original_message() method on callback messages
|
|
822
988
|
from django.dispatch import receiver
|
|
823
989
|
from unicom.signals import telegram_callback_received
|
|
824
990
|
from unicom.services.telegram.create_inline_keyboard import create_inline_keyboard, create_callback_button
|
|
825
991
|
|
|
826
992
|
@receiver(telegram_callback_received)
|
|
827
|
-
def handle_navigation(sender, callback_execution, **kwargs):
|
|
993
|
+
def handle_navigation(sender, callback_execution, clicking_account, original_message, tool_call, **kwargs):
|
|
828
994
|
button_data = callback_execution.callback_data
|
|
829
|
-
callback_msg = callback_execution.callback_message
|
|
830
995
|
|
|
831
996
|
if button_data == 'show_settings':
|
|
832
997
|
# Edit the original message to show settings
|
|
833
|
-
|
|
998
|
+
original_message.edit_original_message({
|
|
834
999
|
'text': '⚙️ Settings Menu',
|
|
835
1000
|
'reply_markup': create_inline_keyboard([
|
|
836
|
-
[create_callback_button("Account", "settings_account")],
|
|
837
|
-
[create_callback_button("Privacy", "settings_privacy")],
|
|
838
|
-
[create_callback_button("🔙 Back", "main_menu")]
|
|
1001
|
+
[create_callback_button("Account", "settings_account", message=original_message, account=clicking_account)],
|
|
1002
|
+
[create_callback_button("Privacy", "settings_privacy", message=original_message, account=clicking_account)],
|
|
1003
|
+
[create_callback_button("🔙 Back", "main_menu", message=original_message, account=clicking_account)]
|
|
839
1004
|
])
|
|
840
1005
|
})
|
|
841
1006
|
```
|
|
@@ -929,35 +1094,78 @@ chat.log_tool_interaction(
|
|
|
929
1094
|
|
|
930
1095
|
The LLM system supports delayed tool calls that can take hours or days to complete, perfect for reminders, monitoring, and long-running processes.
|
|
931
1096
|
|
|
1097
|
+
**Tool Implementation - Return `None` to Defer Response:**
|
|
1098
|
+
|
|
932
1099
|
```python
|
|
933
|
-
|
|
1100
|
+
# In your tool definition (e.g., unibot Tool model)
|
|
1101
|
+
def set_reminder(text: str, delay_hours: int) -> str:
|
|
1102
|
+
"""Schedule a reminder for later"""
|
|
1103
|
+
from django.utils import timezone
|
|
1104
|
+
from datetime import timedelta
|
|
1105
|
+
|
|
1106
|
+
# Schedule the reminder using the tool_call context variable
|
|
1107
|
+
reminder_time = timezone.now() + timedelta(hours=delay_hours)
|
|
1108
|
+
|
|
1109
|
+
# Store tool_call.id for later response
|
|
1110
|
+
# (e.g., in a database, Redis, or scheduler)
|
|
1111
|
+
schedule_reminder(
|
|
1112
|
+
tool_call_id=tool_call.call_id,
|
|
1113
|
+
message=text,
|
|
1114
|
+
scheduled_time=reminder_time
|
|
1115
|
+
)
|
|
934
1116
|
|
|
935
|
-
#
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1117
|
+
# Return None to defer the response
|
|
1118
|
+
# System automatically marks tool_call as IN_PROGRESS
|
|
1119
|
+
return None
|
|
1120
|
+
|
|
1121
|
+
tool_definition = {
|
|
1122
|
+
"name": "set_reminder",
|
|
1123
|
+
"description": "Set a reminder for a specific time in the future",
|
|
1124
|
+
"parameters": {
|
|
1125
|
+
"text": {"type": "string", "description": "Reminder text"},
|
|
1126
|
+
"delay_hours": {"type": "integer", "description": "Hours to wait"}
|
|
942
1127
|
},
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
1128
|
+
"run": set_reminder
|
|
1129
|
+
}
|
|
1130
|
+
```
|
|
1131
|
+
|
|
1132
|
+
**Responding Later from Any Process:**
|
|
1133
|
+
|
|
1134
|
+
```python
|
|
1135
|
+
from unicom.models import ToolCall
|
|
1136
|
+
|
|
1137
|
+
# Hours or days later, in a background job or scheduled task...
|
|
1138
|
+
tool_call = ToolCall.objects.get(call_id="call_123")
|
|
1139
|
+
|
|
1140
|
+
# Respond to the tool call
|
|
1141
|
+
msg, child_request = tool_call.respond("Reminder: Meeting in 1 hour")
|
|
1142
|
+
# This creates a new child request for the LLM to process
|
|
1143
|
+
|
|
1144
|
+
# The LLM receives the response and can continue the conversation
|
|
1145
|
+
```
|
|
1146
|
+
|
|
1147
|
+
**For Periodic/Ongoing Tools (e.g., Monitoring):**
|
|
1148
|
+
|
|
1149
|
+
```python
|
|
1150
|
+
def monitor_system(threshold: int) -> str:
|
|
1151
|
+
"""Monitor system continuously and report status"""
|
|
1152
|
+
|
|
1153
|
+
# Mark as ACTIVE for periodic responses
|
|
1154
|
+
tool_call.mark_active()
|
|
1155
|
+
|
|
1156
|
+
# Start background monitoring
|
|
1157
|
+
start_monitoring_task(tool_call_id=tool_call.call_id, threshold=threshold)
|
|
1158
|
+
|
|
1159
|
+
return None # Or return initial status
|
|
1160
|
+
|
|
1161
|
+
# Later, in your monitoring task...
|
|
1162
|
+
tool_call = ToolCall.objects.get(call_id="monitor_123", status='ACTIVE')
|
|
1163
|
+
|
|
1164
|
+
# Send periodic updates without creating child requests
|
|
1165
|
+
tool_call.respond("CPU usage: 95%") # Just logs, no child request
|
|
1166
|
+
tool_call.respond("CPU usage: 92%") # Just logs, no child request
|
|
948
1167
|
|
|
949
|
-
#
|
|
950
|
-
reminder_call = ToolCall.objects.get(call_id="call_123")
|
|
951
|
-
msg, child_request = reminder_call.respond("Reminder: Meeting in 1 hour")
|
|
952
|
-
# Creates new child request for further processing
|
|
953
|
-
|
|
954
|
-
# For periodic/ongoing tools, set status to ACTIVE
|
|
955
|
-
monitor_call = tool_calls[1]
|
|
956
|
-
monitor_call.status = 'ACTIVE'
|
|
957
|
-
monitor_call.save()
|
|
958
|
-
# Now it can respond indefinitely without creating child requests
|
|
959
|
-
monitor_call.respond("CPU usage: 95%") # Just logs, no child request
|
|
960
|
-
monitor_call.respond("CPU usage: 92%") # Just logs, no child request
|
|
1168
|
+
# Tool call remains ACTIVE and can respond indefinitely
|
|
961
1169
|
```
|
|
962
1170
|
|
|
963
1171
|
#### 🤖 Request Hierarchy and Final Response Logic
|