Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ba58564d29 | |||
| 2079908ce6 | |||
| 4066ab9a44 | |||
| c017c32800 | |||
| fff2839327 | |||
| 752f38b348 | |||
| 7b5fa9a3c3 | |||
| 6540b28437 | |||
| 00c5f66be5 | |||
| 5e4552e4df | |||
| 57ee703f07 | |||
| e8a1a1fb2e | |||
| c26829e9e3 | |||
| d0eb9ee015 | |||
| 2aa76490fd | |||
| 3b8b0b2a52 | |||
| 299fb5724b | |||
| 0084ad01ab | |||
| 0f58815b8d | |||
| b8621214dd | |||
| 61b8dbd0ce | |||
| 977ffb5e32 | |||
| ce0c34825f | |||
| bab5938241 | |||
| 3d281de0c2 | |||
| d495f7dbbe | |||
| acd4cab782 | |||
| f87b54d323 | |||
| bd6c6865b6 | |||
| 200ba314d4 | |||
| c5389931b8 | |||
| 8f5a020891 | |||
| 0c09f1aaca | |||
| c77501c3d9 | |||
| c9db7dc369 | |||
| 006bc45af0 | |||
| 1847b9c90a | |||
| a4bd06ac9e | |||
| 8df5e2d24d | |||
| e42798c0ce | |||
| 533e6705c8 | |||
| 28e7426c2b | |||
| a8c1e211ab | |||
| d6fab25531 | |||
| 37e7101273 | |||
| a014061917 | |||
| ada0f34e54 | |||
| 08e9c065ea | |||
| 8657adb463 | |||
| 04d620ef32 | |||
| 85f149c975 | |||
| 6119e89560 | |||
| e421be1634 | |||
| 3c8edcb5e0 | |||
| aa38a1f5c7 | |||
| a8cef77330 | |||
| 7b6a50998c | |||
| 90caa8754d | |||
| a4b5eb36e1 | |||
| 6fb8b17fe3 | |||
| 7bd1d77be1 |
@@ -0,0 +1,2 @@
|
|||||||
|
mongodb
|
||||||
|
|
||||||
@@ -132,4 +132,6 @@ dmypy.json
|
|||||||
config/config.yml
|
config/config.yml
|
||||||
config/config.env
|
config/config.env
|
||||||
|
|
||||||
docker-compose.dev.yml
|
docker-compose.dev.yml
|
||||||
|
|
||||||
|
mongodb/
|
||||||
@@ -1,20 +1,23 @@
|
|||||||
FROM python:3.8-slim
|
FROM python:3.8-slim
|
||||||
|
|
||||||
ENV PYTHONFAULTHANDLER=1
|
RUN \
|
||||||
ENV PYTHONUNBUFFERED=1
|
set -eux; \
|
||||||
ENV PYTHONHASHSEED=random
|
apt-get update; \
|
||||||
ENV PYTHONDONTWRITEBYTECODE 1
|
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||||
ENV PIP_NO_CACHE_DIR=off
|
python3-pip \
|
||||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=on
|
build-essential \
|
||||||
ENV PIP_DEFAULT_TIMEOUT=100
|
python3-venv \
|
||||||
|
ffmpeg \
|
||||||
|
git \
|
||||||
|
; \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
RUN apt-get update
|
RUN pip3 install -U pip && pip3 install -U wheel && pip3 install -U setuptools==59.5.0
|
||||||
RUN apt-get install -y python3 python3-pip python-dev build-essential python3-venv ffmpeg
|
COPY ./requirements.txt /tmp/requirements.txt
|
||||||
|
RUN pip3 install -r /tmp/requirements.txt && rm -r /tmp/requirements.txt
|
||||||
|
|
||||||
RUN mkdir -p /code
|
COPY . /code
|
||||||
ADD . /code
|
|
||||||
WORKDIR /code
|
WORKDIR /code
|
||||||
|
|
||||||
RUN pip3 install -r requirements.txt
|
CMD ["bash"]
|
||||||
|
|
||||||
CMD ["bash"]
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# ChatGPT Telegram Bot: **Fast. No daily limits. Special chat modes**
|
# ChatGPT Telegram Bot: **GPT-4. Fast. No daily limits. Special chat modes**
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<img src="https://raw.githubusercontent.com/karfly/chatgpt_telegram_bot/main/static/header.png" align="center" style="width: 100%" />
|
<img src="https://raw.githubusercontent.com/karfly/chatgpt_telegram_bot/main/static/header.png" align="center" style="width: 100%" />
|
||||||
@@ -7,26 +7,27 @@
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://t.me/chatgpt_karfly_bot" alt="Run Telegram Bot shield"><img src="https://img.shields.io/badge/RUN-Telegram%20Bot-blue?logo=data:image/svg+xml;base64,PHN2ZyBpZD0iTGl2ZWxsb18xIiBkYXRhLW5hbWU9IkxpdmVsbG8gMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDI0MCAyNDAiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTIwIiB5MT0iMjQwIiB4Mj0iMTIwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWQ5M2QyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzhiMGUzIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHRpdGxlPlRlbGVncmFtX2xvZ288L3RpdGxlPjxjaXJjbGUgY3g9IjEyMCIgY3k9IjEyMCIgcj0iMTIwIiBmaWxsPSJ1cmwoI2xpbmVhci1ncmFkaWVudCkiLz48cGF0aCBkPSJNODEuMjI5LDEyOC43NzJsMTQuMjM3LDM5LjQwNnMxLjc4LDMuNjg3LDMuNjg2LDMuNjg3LDMwLjI1NS0yOS40OTIsMzAuMjU1LTI5LjQ5MmwzMS41MjUtNjAuODlMODEuNzM3LDExOC42WiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0xMDAuMTA2LDEzOC44NzhsLTIuNzMzLDI5LjA0NnMtMS4xNDQsOC45LDcuNzU0LDAsMTcuNDE1LTE1Ljc2MywxNy40MTUtMTUuNzYzIiBmaWxsPSIjYTljNmQ4Ii8+PHBhdGggZD0iTTgxLjQ4NiwxMzAuMTc4LDUyLjIsMTIwLjYzNnMtMy41LTEuNDItMi4zNzMtNC42NGMuMjMyLS42NjQuNy0xLjIyOSwyLjEtMi4yLDYuNDg5LTQuNTIzLDEyMC4xMDYtNDUuMzYsMTIwLjEwNi00NS4zNnMzLjIwOC0xLjA4MSw1LjEtLjM2MmEyLjc2NiwyLjc2NiwwLDAsMSwxLjg4NSwyLjA1NSw5LjM1Nyw5LjM1NywwLDAsMSwuMjU0LDIuNTg1Yy0uMDA5Ljc1Mi0uMSwxLjQ0OS0uMTY5LDIuNTQyLS42OTIsMTEuMTY1LTIxLjQsOTQuNDkzLTIxLjQsOTQuNDkzcy0xLjIzOSw0Ljg3Ni01LjY3OCw1LjA0M0E4LjEzLDguMTMsMCwwLDEsMTQ2LjEsMTcyLjVjLTguNzExLTcuNDkzLTM4LjgxOS0yNy43MjctNDUuNDcyLTMyLjE3N2ExLjI3LDEuMjcsMCwwLDEtLjU0Ni0uOWMtLjA5My0uNDY5LjQxNy0xLjA1LjQxNy0xLjA1czUyLjQyNi00Ni42LDUzLjgyMS01MS40OTJjLjEwOC0uMzc5LS4zLS41NjYtLjg0OC0uNC0zLjQ4MiwxLjI4MS02My44NDQsMzkuNC03MC41MDYsNDMuNjA3QTMuMjEsMy4yMSwwLDAsMSw4MS40ODYsMTMwLjE3OFoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=" width="230"/></a>
|
<a href="https://t.me/chatgpt_karfly_bot?start=source=github" alt="Run Telegram Bot shield"><img src="https://img.shields.io/badge/RUN-Telegram%20Bot-blue?logo=data:image/svg+xml;base64,PHN2ZyBpZD0iTGl2ZWxsb18xIiBkYXRhLW5hbWU9IkxpdmVsbG8gMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmlld0JveD0iMCAwIDI0MCAyNDAiPjxkZWZzPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTIwIiB5MT0iMjQwIiB4Mj0iMTIwIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMWQ5M2QyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzhiMGUzIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHRpdGxlPlRlbGVncmFtX2xvZ288L3RpdGxlPjxjaXJjbGUgY3g9IjEyMCIgY3k9IjEyMCIgcj0iMTIwIiBmaWxsPSJ1cmwoI2xpbmVhci1ncmFkaWVudCkiLz48cGF0aCBkPSJNODEuMjI5LDEyOC43NzJsMTQuMjM3LDM5LjQwNnMxLjc4LDMuNjg3LDMuNjg2LDMuNjg3LDMwLjI1NS0yOS40OTIsMzAuMjU1LTI5LjQ5MmwzMS41MjUtNjAuODlMODEuNzM3LDExOC42WiIgZmlsbD0iI2M4ZGFlYSIvPjxwYXRoIGQ9Ik0xMDAuMTA2LDEzOC44NzhsLTIuNzMzLDI5LjA0NnMtMS4xNDQsOC45LDcuNzU0LDAsMTcuNDE1LTE1Ljc2MywxNy40MTUtMTUuNzYzIiBmaWxsPSIjYTljNmQ4Ii8+PHBhdGggZD0iTTgxLjQ4NiwxMzAuMTc4LDUyLjIsMTIwLjYzNnMtMy41LTEuNDItMi4zNzMtNC42NGMuMjMyLS42NjQuNy0xLjIyOSwyLjEtMi4yLDYuNDg5LTQuNTIzLDEyMC4xMDYtNDUuMzYsMTIwLjEwNi00NS4zNnMzLjIwOC0xLjA4MSw1LjEtLjM2MmEyLjc2NiwyLjc2NiwwLDAsMSwxLjg4NSwyLjA1NSw5LjM1Nyw5LjM1NywwLDAsMSwuMjU0LDIuNTg1Yy0uMDA5Ljc1Mi0uMSwxLjQ0OS0uMTY5LDIuNTQyLS42OTIsMTEuMTY1LTIxLjQsOTQuNDkzLTIxLjQsOTQuNDkzcy0xLjIzOSw0Ljg3Ni01LjY3OCw1LjA0M0E4LjEzLDguMTMsMCwwLDEsMTQ2LjEsMTcyLjVjLTguNzExLTcuNDkzLTM4LjgxOS0yNy43MjctNDUuNDcyLTMyLjE3N2ExLjI3LDEuMjcsMCwwLDEtLjU0Ni0uOWMtLjA5My0uNDY5LjQxNy0xLjA1LjQxNy0xLjA1czUyLjQyNi00Ni42LDUzLjgyMS01MS40OTJjLjEwOC0uMzc5LS4zLS41NjYtLjg0OC0uNC0zLjQ4MiwxLjI4MS02My44NDQsMzkuNC03MC41MDYsNDMuNjA3QTMuMjEsMy4yMSwwLDAsMSw4MS40ODYsMTMwLjE3OFoiIGZpbGw9IiNmZmYiLz48L3N2Zz4=" width="230"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://github.com/karfly/chatgpt_telegram_bot/blob/main/static/donate/donate.md#%EF%B8%8F-donate" alt="Donate shield"><img src="https://img.shields.io/badge/-Donate-red?logo=undertale" width="100"/></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
We all love [chat.openai.com](https://chat.openai.com), but... It's TERRIBLY laggy, has daily limits, and is only accessible through an archaic web interface.
|
We all love [chat.openai.com](https://chat.openai.com), but... It's TERRIBLY laggy, has daily limits, and is only accessible through an archaic web interface.
|
||||||
|
|
||||||
This repo is ChatGPT re-created with GPT-3.5 LLM as Telegram Bot. **And it works great.**
|
This repo is ChatGPT re-created as Telegram Bot. **And it works great.**
|
||||||
|
|
||||||
You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/chatgpt_karfly_bot)
|
You can deploy your own bot, or use mine: [@jadvebot](https://t.me/jadvebot) (Our web: https://jadve.com)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Low latency replies (it usually takes about 3-5 seconds)
|
- Low latency replies (it usually takes about 3-5 seconds)
|
||||||
- No request limits
|
- No request limits
|
||||||
- Message streaming (watch demo)
|
- Message streaming (watch demo)
|
||||||
|
- GPT-4 and GPT-4 Turbo support
|
||||||
|
- GPT-4 Vision support
|
||||||
|
- Group Chat support (/help_group_chat to get instructions)
|
||||||
|
- DALLE 2 (choose 👩🎨 Artist mode to generate images)
|
||||||
- Voice message recognition
|
- Voice message recognition
|
||||||
- Code highlighting
|
- Code highlighting
|
||||||
- Special chat modes: 👩🏼🎓 Assistant, 👩🏼💻 Code Assistant, 📝 Text Improver and 🎬 Movie Expert. You can easily create your own chat modes by editing `config/chat_modes.yml`
|
- 15 special chat modes: 👩🏼🎓 Assistant, 👩🏼💻 Code Assistant, 👩🎨 Artist, 🧠 Psychologist, 🚀 Elon Musk and other. You can easily create your own chat modes by editing `config/chat_modes.yml`
|
||||||
- Support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction)
|
- Support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction)
|
||||||
- List of allowed Telegram users
|
- List of allowed Telegram users
|
||||||
- Track $ balance spent on OpenAI API
|
- Track $ balance spent on OpenAI API
|
||||||
@@ -38,16 +39,25 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
|
|||||||
---
|
---
|
||||||
|
|
||||||
## News
|
## News
|
||||||
|
- *21 Apr 2023*:
|
||||||
|
- DALLE 2 support
|
||||||
|
- Group Chat support (/help_group_chat to get instructions)
|
||||||
|
- 10 new hot chat modes and updated chat mode menu with pagination: 🇬🇧 English Tutor, 🧠 Psychologist, 🚀 Elon Musk, 📊 SQL Assistant and other.
|
||||||
|
- *24 Mar 2023*: GPT-4 support. Run `/settings` command to choose model
|
||||||
- *15 Mar 2023*: Added message streaming. Now you don't have to wait until the whole message is ready, it's streamed to Telegram part-by-part (watch demo)
|
- *15 Mar 2023*: Added message streaming. Now you don't have to wait until the whole message is ready, it's streamed to Telegram part-by-part (watch demo)
|
||||||
- *9 Mar 2023*: Now you can easily create your own Chat Modes by editing `config/chat_modes.yml`
|
- *9 Mar 2023*: Now you can easily create your own Chat Modes by editing `config/chat_modes.yml`
|
||||||
- *8 Mar 2023*: Added voice message recognition with [OpenAI Whisper API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis). Record a voice message and ChatGPT will answer you!
|
- *8 Mar 2023*: Added voice message recognition with [OpenAI Whisper API](https://openai.com/blog/introducing-chatgpt-and-whisper-apis). Record a voice message and ChatGPT will answer you!
|
||||||
- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction). It's enabled by default and can be disabled with `use_chatgpt_api` option in config. Don't forget to **rebuild** you docker image (`--build`).
|
- *2 Mar 2023*: Added support of [ChatGPT API](https://platform.openai.com/docs/guides/chat/introduction).
|
||||||
|
- *1 Aug 2023*: Added OpenAI API Base to config (useful while using OpenAI-compatible API like [LocalAI](https://github.com/go-skynet/LocalAI))
|
||||||
|
- *15 Nov 2023*: Added support of [GPT-4 Turbo](https://help.openai.com/en/articles/8555510-gpt-4-turbo)
|
||||||
|
- *2 Apt 2024*: Added [GPT-4 Vision](https://platform.openai.com/docs/guides/vision) support
|
||||||
|
|
||||||
## Bot commands
|
## Bot commands
|
||||||
- `/retry` – Regenerate last bot answer
|
- `/retry` – Regenerate last bot answer
|
||||||
- `/new` – Start new dialog
|
- `/new` – Start new dialog
|
||||||
- `/mode` – Select chat mode
|
- `/mode` – Select chat mode
|
||||||
- `/balance` – Show balance
|
- `/balance` – Show balance
|
||||||
|
- `/settings` – Show settings
|
||||||
- `/help` – Show help
|
- `/help` – Show help
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
@@ -67,11 +77,37 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
|
|||||||
```
|
```
|
||||||
|
|
||||||
## ❤️ Top donations
|
## ❤️ Top donations
|
||||||
You can be in this list: <a href="https://github.com/karfly/chatgpt_telegram_bot/blob/main/static/donate/donate.md#%EF%B8%8F-donate" alt="Donate shield"><img src="https://img.shields.io/badge/-Donate-red?logo=undertale" /></a>
|
You can be in this list:
|
||||||
|
|
||||||
|
1. [LilRocco](https://t.me/LilRocco). Donation: **11000$** (!!!)
|
||||||
|
|
||||||
|
1. [Mr V](https://t.me/mr_v_v_v). Donation **250$**
|
||||||
|
|
||||||
|
1. [unexpectedsunday](https://t.me/unexpectedsunday). Donation: **150$**
|
||||||
|
|
||||||
|
1. [Sem](https://t.me/sembrestels). Donation: **100$**
|
||||||
|
|
||||||
|
1. [Miksolo](https://t.me/Miksolo). Donation: **81$**
|
||||||
|
|
||||||
|
*Message:* Thank you. Using this docker container every day! Actually created the same project but its good to see that this one is being supported often. Will continue using it! Good architecture choices made in the code 💪!
|
||||||
|
|
||||||
|
1. [Ryo](https://t.me/ryokihara). Donation: **80$**
|
||||||
|
|
||||||
1. [Ilias Ism](https://twitter.com/illyism). Donation: **69$**
|
1. [Ilias Ism](https://twitter.com/illyism). Donation: **69$**
|
||||||
|
|
||||||
*Message:* I wanted to thank you for your amazing code! It helped me start my own Telegram ChatGPT bot and add a bunch of cool features. I really appreciate your hard work on this project. For anyone interested in trying my bot, feel free to check it out here: [magicbuddy.chat](https://magicbuddy.chat) 🤖 Thanks again! 😊
|
*Message:* I wanted to thank you for your amazing code! It helped me start my own Telegram ChatGPT bot and add a bunch of cool features. I really appreciate your hard work on this project. For anyone interested in trying my bot, feel free to check it out here: [magicbuddy.chat](https://magicbuddy.chat) 🤖 Thanks again! 😊
|
||||||
|
|
||||||
|
1. [Sebastian](https://t.me/dell1503). Donation: **55$**
|
||||||
|
|
||||||
|
1. [Alexander Zimin](https://t.me/azimin). Donation: **50$**
|
||||||
|
|
||||||
|
1. [Kbaji20](https://t.me/Kbaji20). Donation: **30$**
|
||||||
|
|
||||||
|
1. [Hans Blinken](https://t.me/hblink). Donation: **10$**
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
- Main contributor: @karfly
|
||||||
|
- [Jadve AI](https://jadve.com).
|
||||||
|
|
||||||
## References
|
## References
|
||||||
1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt)
|
1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt)
|
||||||
|
|||||||
@@ -1,20 +1,18 @@
|
|||||||
import os
|
import io
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
import traceback
|
import traceback
|
||||||
import html
|
import html
|
||||||
import json
|
import json
|
||||||
import tempfile
|
|
||||||
import pydub
|
|
||||||
from pathlib import Path
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
import openai
|
||||||
|
|
||||||
import telegram
|
import telegram
|
||||||
from telegram import (
|
from telegram import (
|
||||||
Update,
|
Update,
|
||||||
User,
|
User,
|
||||||
InlineKeyboardButton,
|
InlineKeyboardButton,
|
||||||
InlineKeyboardMarkup,
|
InlineKeyboardMarkup,
|
||||||
BotCommand
|
BotCommand
|
||||||
)
|
)
|
||||||
from telegram.ext import (
|
from telegram.ext import (
|
||||||
@@ -33,18 +31,37 @@ import config
|
|||||||
import database
|
import database
|
||||||
import openai_utils
|
import openai_utils
|
||||||
|
|
||||||
|
import base64
|
||||||
|
|
||||||
# setup
|
# setup
|
||||||
db = database.Database()
|
db = database.Database()
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
user_semaphores = {}
|
user_semaphores = {}
|
||||||
|
user_tasks = {}
|
||||||
|
|
||||||
HELP_MESSAGE = """Commands:
|
HELP_MESSAGE = """Commands:
|
||||||
⚪ /retry – Regenerate last bot answer
|
⚪ /retry – Regenerate last bot answer
|
||||||
⚪ /new – Start new dialog
|
⚪ /new – Start new dialog
|
||||||
⚪ /mode – Select chat mode
|
⚪ /mode – Select chat mode
|
||||||
|
⚪ /settings – Show settings
|
||||||
⚪ /balance – Show balance
|
⚪ /balance – Show balance
|
||||||
⚪ /help – Show help
|
⚪ /help – Show help
|
||||||
|
|
||||||
|
🎨 Generate images from text prompts in <b>👩🎨 Artist</b> /mode
|
||||||
|
👥 Add bot to <b>group chat</b>: /help_group_chat
|
||||||
|
🎤 You can send <b>Voice Messages</b> instead of text
|
||||||
|
"""
|
||||||
|
|
||||||
|
HELP_GROUP_CHAT_MESSAGE = """You can add bot to any <b>group chat</b> to help and entertain its participants!
|
||||||
|
|
||||||
|
Instructions (see <b>video</b> below):
|
||||||
|
1. Add the bot to the group chat
|
||||||
|
2. Make it an <b>admin</b>, so that it can see messages (all other rights can be restricted)
|
||||||
|
3. You're awesome!
|
||||||
|
|
||||||
|
To get a reply from the bot in the chat – @ <b>tag</b> it or <b>reply</b> to its message.
|
||||||
|
For example: "{bot_username} write a poem about Telegram"
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@@ -70,20 +87,60 @@ async def register_user_if_not_exists(update: Update, context: CallbackContext,
|
|||||||
if user.id not in user_semaphores:
|
if user.id not in user_semaphores:
|
||||||
user_semaphores[user.id] = asyncio.Semaphore(1)
|
user_semaphores[user.id] = asyncio.Semaphore(1)
|
||||||
|
|
||||||
|
if db.get_user_attribute(user.id, "current_model") is None:
|
||||||
|
db.set_user_attribute(user.id, "current_model", config.models["available_text_models"][0])
|
||||||
|
|
||||||
|
# back compatibility for n_used_tokens field
|
||||||
|
n_used_tokens = db.get_user_attribute(user.id, "n_used_tokens")
|
||||||
|
if isinstance(n_used_tokens, int) or isinstance(n_used_tokens, float): # old format
|
||||||
|
new_n_used_tokens = {
|
||||||
|
"gpt-3.5-turbo": {
|
||||||
|
"n_input_tokens": 0,
|
||||||
|
"n_output_tokens": n_used_tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.set_user_attribute(user.id, "n_used_tokens", new_n_used_tokens)
|
||||||
|
|
||||||
|
# voice message transcription
|
||||||
|
if db.get_user_attribute(user.id, "n_transcribed_seconds") is None:
|
||||||
|
db.set_user_attribute(user.id, "n_transcribed_seconds", 0.0)
|
||||||
|
|
||||||
|
# image generation
|
||||||
|
if db.get_user_attribute(user.id, "n_generated_images") is None:
|
||||||
|
db.set_user_attribute(user.id, "n_generated_images", 0)
|
||||||
|
|
||||||
|
|
||||||
|
async def is_bot_mentioned(update: Update, context: CallbackContext):
|
||||||
|
try:
|
||||||
|
message = update.message
|
||||||
|
|
||||||
|
if message.chat.type == "private":
|
||||||
|
return True
|
||||||
|
|
||||||
|
if message.text is not None and ("@" + context.bot.username) in message.text:
|
||||||
|
return True
|
||||||
|
|
||||||
|
if message.reply_to_message is not None:
|
||||||
|
if message.reply_to_message.from_user.id == context.bot.id:
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
async def start_handle(update: Update, context: CallbackContext):
|
async def start_handle(update: Update, context: CallbackContext):
|
||||||
await register_user_if_not_exists(update, context, update.message.from_user)
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
|
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
db.start_new_dialog(user_id)
|
db.start_new_dialog(user_id)
|
||||||
|
|
||||||
reply_text = "Hi! I'm <b>ChatGPT</b> bot implemented with GPT-3.5 OpenAI API 🤖\n\n"
|
reply_text = "Hi! I'm <b>ChatGPT</b> bot implemented with OpenAI API 🤖\n\n"
|
||||||
reply_text += HELP_MESSAGE
|
reply_text += HELP_MESSAGE
|
||||||
|
|
||||||
reply_text += "\nAnd now... ask me anything!"
|
|
||||||
|
|
||||||
await update.message.reply_text(reply_text, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(reply_text, parse_mode=ParseMode.HTML)
|
||||||
|
await show_chat_modes_handle(update, context)
|
||||||
|
|
||||||
|
|
||||||
async def help_handle(update: Update, context: CallbackContext):
|
async def help_handle(update: Update, context: CallbackContext):
|
||||||
@@ -93,10 +150,21 @@ async def help_handle(update: Update, context: CallbackContext):
|
|||||||
await update.message.reply_text(HELP_MESSAGE, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(HELP_MESSAGE, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
async def help_group_chat_handle(update: Update, context: CallbackContext):
|
||||||
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
text = HELP_GROUP_CHAT_MESSAGE.format(bot_username="@" + context.bot.username)
|
||||||
|
|
||||||
|
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
await update.message.reply_video(config.help_group_chat_video_path)
|
||||||
|
|
||||||
|
|
||||||
async def retry_handle(update: Update, context: CallbackContext):
|
async def retry_handle(update: Update, context: CallbackContext):
|
||||||
await register_user_if_not_exists(update, context, update.message.from_user)
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
if await is_previous_message_not_answered_yet(update, context): return
|
if await is_previous_message_not_answered_yet(update, context): return
|
||||||
|
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
@@ -110,104 +178,272 @@ async def retry_handle(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
await message_handle(update, context, message=last_dialog_message["user"], use_new_dialog_timeout=False)
|
await message_handle(update, context, message=last_dialog_message["user"], use_new_dialog_timeout=False)
|
||||||
|
|
||||||
|
async def _vision_message_handle_fn(
|
||||||
|
update: Update, context: CallbackContext, use_new_dialog_timeout: bool = True
|
||||||
|
):
|
||||||
|
logger.info('_vision_message_handle_fn')
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
current_model = db.get_user_attribute(user_id, "current_model")
|
||||||
|
|
||||||
|
if current_model != "gpt-4-vision-preview" and current_model != "gpt-4o":
|
||||||
|
await update.message.reply_text(
|
||||||
|
"🥲 Images processing is only available for <b>gpt-4-vision-preview</b> and <b>gpt-4o</b> model. Please change your settings in /settings",
|
||||||
|
parse_mode=ParseMode.HTML,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
|
||||||
|
|
||||||
|
# new dialog timeout
|
||||||
|
if use_new_dialog_timeout:
|
||||||
|
if (datetime.now() - db.get_user_attribute(user_id, "last_interaction")).seconds > config.new_dialog_timeout and len(db.get_dialog_messages(user_id)) > 0:
|
||||||
|
db.start_new_dialog(user_id)
|
||||||
|
await update.message.reply_text(f"Starting new dialog due to timeout (<b>{config.chat_modes[chat_mode]['name']}</b> mode) ✅", parse_mode=ParseMode.HTML)
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
buf = None
|
||||||
|
if update.message.effective_attachment:
|
||||||
|
photo = update.message.effective_attachment[-1]
|
||||||
|
photo_file = await context.bot.get_file(photo.file_id)
|
||||||
|
|
||||||
|
# store file in memory, not on disk
|
||||||
|
buf = io.BytesIO()
|
||||||
|
await photo_file.download_to_memory(buf)
|
||||||
|
buf.name = "image.jpg" # file extension is required
|
||||||
|
buf.seek(0) # move cursor to the beginning of the buffer
|
||||||
|
|
||||||
|
# in case of CancelledError
|
||||||
|
n_input_tokens, n_output_tokens = 0, 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# send placeholder message to user
|
||||||
|
placeholder_message = await update.message.reply_text("...")
|
||||||
|
message = update.message.caption or update.message.text or ''
|
||||||
|
|
||||||
|
# send typing action
|
||||||
|
await update.message.chat.send_action(action="typing")
|
||||||
|
|
||||||
|
dialog_messages = db.get_dialog_messages(user_id, dialog_id=None)
|
||||||
|
parse_mode = {"html": ParseMode.HTML, "markdown": ParseMode.MARKDOWN}[
|
||||||
|
config.chat_modes[chat_mode]["parse_mode"]
|
||||||
|
]
|
||||||
|
|
||||||
|
chatgpt_instance = openai_utils.ChatGPT(model=current_model)
|
||||||
|
if config.enable_message_streaming:
|
||||||
|
gen = chatgpt_instance.send_vision_message_stream(
|
||||||
|
message,
|
||||||
|
dialog_messages=dialog_messages,
|
||||||
|
image_buffer=buf,
|
||||||
|
chat_mode=chat_mode,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
(
|
||||||
|
answer,
|
||||||
|
(n_input_tokens, n_output_tokens),
|
||||||
|
n_first_dialog_messages_removed,
|
||||||
|
) = await chatgpt_instance.send_vision_message(
|
||||||
|
message,
|
||||||
|
dialog_messages=dialog_messages,
|
||||||
|
image_buffer=buf,
|
||||||
|
chat_mode=chat_mode,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fake_gen():
|
||||||
|
yield "finished", answer, (
|
||||||
|
n_input_tokens,
|
||||||
|
n_output_tokens,
|
||||||
|
), n_first_dialog_messages_removed
|
||||||
|
|
||||||
|
gen = fake_gen()
|
||||||
|
|
||||||
|
prev_answer = ""
|
||||||
|
async for gen_item in gen:
|
||||||
|
(
|
||||||
|
status,
|
||||||
|
answer,
|
||||||
|
(n_input_tokens, n_output_tokens),
|
||||||
|
n_first_dialog_messages_removed,
|
||||||
|
) = gen_item
|
||||||
|
|
||||||
|
answer = answer[:4096] # telegram message limit
|
||||||
|
|
||||||
|
# update only when 100 new symbols are ready
|
||||||
|
if abs(len(answer) - len(prev_answer)) < 100 and status != "finished":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
await context.bot.edit_message_text(
|
||||||
|
answer,
|
||||||
|
chat_id=placeholder_message.chat_id,
|
||||||
|
message_id=placeholder_message.message_id,
|
||||||
|
parse_mode=parse_mode,
|
||||||
|
)
|
||||||
|
except telegram.error.BadRequest as e:
|
||||||
|
if str(e).startswith("Message is not modified"):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
await context.bot.edit_message_text(
|
||||||
|
answer,
|
||||||
|
chat_id=placeholder_message.chat_id,
|
||||||
|
message_id=placeholder_message.message_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
await asyncio.sleep(0.01) # wait a bit to avoid flooding
|
||||||
|
|
||||||
|
prev_answer = answer
|
||||||
|
|
||||||
|
# update user data
|
||||||
|
if buf is not None:
|
||||||
|
base_image = base64.b64encode(buf.getvalue()).decode("utf-8")
|
||||||
|
new_dialog_message = {"user": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": message,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image",
|
||||||
|
"image": base_image,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
, "bot": answer, "date": datetime.now()}
|
||||||
|
else:
|
||||||
|
new_dialog_message = {"user": [{"type": "text", "text": message}], "bot": answer, "date": datetime.now()}
|
||||||
|
|
||||||
|
db.set_dialog_messages(
|
||||||
|
user_id,
|
||||||
|
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
|
||||||
|
dialog_id=None
|
||||||
|
)
|
||||||
|
|
||||||
|
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# note: intermediate token updates only work when enable_message_streaming=True (config.yml)
|
||||||
|
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
|
||||||
|
raise
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
error_text = f"Something went wrong during completion. Reason: {e}"
|
||||||
|
logger.error(error_text)
|
||||||
|
await update.message.reply_text(error_text)
|
||||||
|
return
|
||||||
|
|
||||||
|
async def unsupport_message_handle(update: Update, context: CallbackContext, message=None):
|
||||||
|
error_text = f"I don't know how to read files or videos. Send the picture in normal mode (Quick Mode)."
|
||||||
|
logger.error(error_text)
|
||||||
|
await update.message.reply_text(error_text)
|
||||||
|
return
|
||||||
|
|
||||||
async def message_handle(update: Update, context: CallbackContext, message=None, use_new_dialog_timeout=True):
|
async def message_handle(update: Update, context: CallbackContext, message=None, use_new_dialog_timeout=True):
|
||||||
|
# check if bot was mentioned (for group chats)
|
||||||
|
if not await is_bot_mentioned(update, context):
|
||||||
|
return
|
||||||
|
|
||||||
# check if message is edited
|
# check if message is edited
|
||||||
if update.edited_message is not None:
|
if update.edited_message is not None:
|
||||||
await edited_message_handle(update, context)
|
await edited_message_handle(update, context)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
_message = message or update.message.text
|
||||||
|
|
||||||
|
# remove bot mention (in group chats)
|
||||||
|
if update.message.chat.type != "private":
|
||||||
|
_message = _message.replace("@" + context.bot.username, "").strip()
|
||||||
|
|
||||||
await register_user_if_not_exists(update, context, update.message.from_user)
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
if await is_previous_message_not_answered_yet(update, context): return
|
if await is_previous_message_not_answered_yet(update, context): return
|
||||||
|
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
|
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
|
||||||
|
|
||||||
async with user_semaphores[user_id]:
|
if chat_mode == "artist":
|
||||||
|
await generate_image_handle(update, context, message=message)
|
||||||
|
return
|
||||||
|
|
||||||
|
current_model = db.get_user_attribute(user_id, "current_model")
|
||||||
|
|
||||||
|
async def message_handle_fn():
|
||||||
# new dialog timeout
|
# new dialog timeout
|
||||||
if use_new_dialog_timeout:
|
if use_new_dialog_timeout:
|
||||||
if (datetime.now() - db.get_user_attribute(user_id, "last_interaction")).seconds > config.new_dialog_timeout and len(db.get_dialog_messages(user_id)) > 0:
|
if (datetime.now() - db.get_user_attribute(user_id, "last_interaction")).seconds > config.new_dialog_timeout and len(db.get_dialog_messages(user_id)) > 0:
|
||||||
db.start_new_dialog(user_id)
|
db.start_new_dialog(user_id)
|
||||||
await update.message.reply_text(f"Starting new dialog due to timeout (<b>{openai_utils.CHAT_MODES[chat_mode]['name']}</b> mode) ✅", parse_mode=ParseMode.HTML)
|
await update.message.reply_text(f"Starting new dialog due to timeout (<b>{config.chat_modes[chat_mode]['name']}</b> mode) ✅", parse_mode=ParseMode.HTML)
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
# send typing action
|
# in case of CancelledError
|
||||||
await update.message.chat.send_action(action="typing")
|
n_input_tokens, n_output_tokens = 0, 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = message or update.message.text
|
# send placeholder message to user
|
||||||
|
placeholder_message = await update.message.reply_text("...")
|
||||||
|
|
||||||
|
# send typing action
|
||||||
|
await update.message.chat.send_action(action="typing")
|
||||||
|
|
||||||
|
if _message is None or len(_message) == 0:
|
||||||
|
await update.message.reply_text("🥲 You sent <b>empty message</b>. Please, try again!", parse_mode=ParseMode.HTML)
|
||||||
|
return
|
||||||
|
|
||||||
dialog_messages = db.get_dialog_messages(user_id, dialog_id=None)
|
dialog_messages = db.get_dialog_messages(user_id, dialog_id=None)
|
||||||
parse_mode = {
|
parse_mode = {
|
||||||
"html": ParseMode.HTML,
|
"html": ParseMode.HTML,
|
||||||
"markdown": ParseMode.MARKDOWN
|
"markdown": ParseMode.MARKDOWN
|
||||||
}[openai_utils.CHAT_MODES[chat_mode]["parse_mode"]]
|
}[config.chat_modes[chat_mode]["parse_mode"]]
|
||||||
|
|
||||||
chatgpt_instance = openai_utils.ChatGPT(use_chatgpt_api=config.use_chatgpt_api)
|
chatgpt_instance = openai_utils.ChatGPT(model=current_model)
|
||||||
if config.enable_message_streaming:
|
if config.enable_message_streaming:
|
||||||
gen = chatgpt_instance.send_message_stream(message, dialog_messages=dialog_messages, chat_mode=chat_mode)
|
gen = chatgpt_instance.send_message_stream(_message, dialog_messages=dialog_messages, chat_mode=chat_mode)
|
||||||
else:
|
else:
|
||||||
answer, n_used_tokens, n_first_dialog_messages_removed = await chatgpt_instance.send_message(
|
answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed = await chatgpt_instance.send_message(
|
||||||
message,
|
_message,
|
||||||
dialog_messages=dialog_messages,
|
dialog_messages=dialog_messages,
|
||||||
chat_mode=chat_mode
|
chat_mode=chat_mode
|
||||||
)
|
)
|
||||||
|
|
||||||
async def fake_gen():
|
async def fake_gen():
|
||||||
yield "finished", answer, n_used_tokens, n_first_dialog_messages_removed
|
yield "finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
|
||||||
|
|
||||||
gen = fake_gen()
|
gen = fake_gen()
|
||||||
|
|
||||||
# send message to user
|
|
||||||
prev_answer = ""
|
prev_answer = ""
|
||||||
i = -1
|
|
||||||
async for gen_item in gen:
|
async for gen_item in gen:
|
||||||
i += 1
|
status, answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed = gen_item
|
||||||
|
|
||||||
status = gen_item[0]
|
|
||||||
if status == "not_finished":
|
|
||||||
status, answer = gen_item
|
|
||||||
elif status == "finished":
|
|
||||||
status, answer, n_used_tokens, n_first_dialog_messages_removed = gen_item
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Streaming status {status} is unknown")
|
|
||||||
|
|
||||||
answer = answer[:4096] # telegram message limit
|
answer = answer[:4096] # telegram message limit
|
||||||
if i == 0: # send first message (then it'll be edited if message streaming is enabled)
|
|
||||||
try:
|
|
||||||
sent_message = await update.message.reply_text(answer, parse_mode=parse_mode)
|
|
||||||
except telegram.error.BadRequest as e:
|
|
||||||
if str(e).startswith("Message must be non-empty"): # first answer chunk from openai was empty
|
|
||||||
i = -1 # try again to send first message
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
sent_message = await update.message.reply_text(answer)
|
|
||||||
else: # edit sent message
|
|
||||||
# update only when 100 new symbols are ready
|
|
||||||
if abs(len(answer) - len(prev_answer)) < 100 and status != "finished":
|
|
||||||
continue
|
|
||||||
|
|
||||||
try:
|
|
||||||
await context.bot.edit_message_text(answer, chat_id=sent_message.chat_id, message_id=sent_message.message_id, parse_mode=parse_mode)
|
|
||||||
except telegram.error.BadRequest as e:
|
|
||||||
if str(e).startswith("Message is not modified"):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
await context.bot.edit_message_text(answer, chat_id=sent_message.chat_id, message_id=sent_message.message_id)
|
|
||||||
|
|
||||||
await asyncio.sleep(0.01) # wait a bit to avoid flooding
|
|
||||||
|
|
||||||
prev_answer = answer
|
# update only when 100 new symbols are ready
|
||||||
|
if abs(len(answer) - len(prev_answer)) < 100 and status != "finished":
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
await context.bot.edit_message_text(answer, chat_id=placeholder_message.chat_id, message_id=placeholder_message.message_id, parse_mode=parse_mode)
|
||||||
|
except telegram.error.BadRequest as e:
|
||||||
|
if str(e).startswith("Message is not modified"):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
await context.bot.edit_message_text(answer, chat_id=placeholder_message.chat_id, message_id=placeholder_message.message_id)
|
||||||
|
|
||||||
|
await asyncio.sleep(0.01) # wait a bit to avoid flooding
|
||||||
|
|
||||||
|
prev_answer = answer
|
||||||
|
|
||||||
# update user data
|
# update user data
|
||||||
new_dialog_message = {"user": message, "bot": answer, "date": datetime.now()}
|
new_dialog_message = {"user": [{"type": "text", "text": _message}], "bot": answer, "date": datetime.now()}
|
||||||
|
|
||||||
db.set_dialog_messages(
|
db.set_dialog_messages(
|
||||||
user_id,
|
user_id,
|
||||||
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
|
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
|
||||||
dialog_id=None
|
dialog_id=None
|
||||||
)
|
)
|
||||||
|
|
||||||
db.set_user_attribute(user_id, "n_used_tokens", n_used_tokens + db.get_user_attribute(user_id, "n_used_tokens"))
|
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
|
||||||
|
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
# note: intermediate token updates only work when enable_message_streaming=True (config.yml)
|
||||||
|
db.update_n_used_tokens(user_id, current_model, n_input_tokens, n_output_tokens)
|
||||||
|
raise
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
error_text = f"Something went wrong during completion. Reason: {e}"
|
error_text = f"Something went wrong during completion. Reason: {e}"
|
||||||
logger.error(error_text)
|
logger.error(error_text)
|
||||||
@@ -222,13 +458,43 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
|||||||
text = f"✍️ <i>Note:</i> Your current dialog is too long, so <b>{n_first_dialog_messages_removed} first messages</b> were removed from the context.\n Send /new command to start new dialog"
|
text = f"✍️ <i>Note:</i> Your current dialog is too long, so <b>{n_first_dialog_messages_removed} first messages</b> were removed from the context.\n Send /new command to start new dialog"
|
||||||
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
async with user_semaphores[user_id]:
|
||||||
|
if current_model == "gpt-4-vision-preview" or current_model == "gpt-4o" or update.message.photo is not None and len(update.message.photo) > 0:
|
||||||
|
|
||||||
|
logger.error(current_model)
|
||||||
|
# What is this? ^^^
|
||||||
|
|
||||||
|
if current_model != "gpt-4o" and current_model != "gpt-4-vision-preview":
|
||||||
|
current_model = "gpt-4o"
|
||||||
|
db.set_user_attribute(user_id, "current_model", "gpt-4o")
|
||||||
|
task = asyncio.create_task(
|
||||||
|
_vision_message_handle_fn(update, context, use_new_dialog_timeout=use_new_dialog_timeout)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
task = asyncio.create_task(
|
||||||
|
message_handle_fn()
|
||||||
|
)
|
||||||
|
|
||||||
|
user_tasks[user_id] = task
|
||||||
|
|
||||||
|
try:
|
||||||
|
await task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
await update.message.reply_text("✅ Canceled", parse_mode=ParseMode.HTML)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
if user_id in user_tasks:
|
||||||
|
del user_tasks[user_id]
|
||||||
|
|
||||||
|
|
||||||
async def is_previous_message_not_answered_yet(update: Update, context: CallbackContext):
|
async def is_previous_message_not_answered_yet(update: Update, context: CallbackContext):
|
||||||
await register_user_if_not_exists(update, context, update.message.from_user)
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
|
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
if user_semaphores[user_id].locked():
|
if user_semaphores[user_id].locked():
|
||||||
text = "⏳ Please <b>wait</b> for a reply to the previous message"
|
text = "⏳ Please <b>wait</b> for a reply to the previous message\n"
|
||||||
|
text += "Or you can /cancel it"
|
||||||
await update.message.reply_text(text, reply_to_message_id=update.message.id, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(text, reply_to_message_id=update.message.id, parse_mode=ParseMode.HTML)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
@@ -236,6 +502,10 @@ async def is_previous_message_not_answered_yet(update: Update, context: Callback
|
|||||||
|
|
||||||
|
|
||||||
async def voice_message_handle(update: Update, context: CallbackContext):
|
async def voice_message_handle(update: Update, context: CallbackContext):
|
||||||
|
# check if bot was mentioned (for group chats)
|
||||||
|
if not await is_bot_mentioned(update, context):
|
||||||
|
return
|
||||||
|
|
||||||
await register_user_if_not_exists(update, context, update.message.from_user)
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
if await is_previous_message_not_answered_yet(update, context): return
|
if await is_previous_message_not_answered_yet(update, context): return
|
||||||
|
|
||||||
@@ -243,34 +513,51 @@ async def voice_message_handle(update: Update, context: CallbackContext):
|
|||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
voice = update.message.voice
|
voice = update.message.voice
|
||||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
voice_file = await context.bot.get_file(voice.file_id)
|
||||||
tmp_dir = Path(tmp_dir)
|
|
||||||
voice_ogg_path = tmp_dir / "voice.ogg"
|
# store file in memory, not on disk
|
||||||
|
buf = io.BytesIO()
|
||||||
# download
|
await voice_file.download_to_memory(buf)
|
||||||
voice_file = await context.bot.get_file(voice.file_id)
|
buf.name = "voice.oga" # file extension is required
|
||||||
await voice_file.download_to_drive(voice_ogg_path)
|
buf.seek(0) # move cursor to the beginning of the buffer
|
||||||
|
|
||||||
# convert to mp3
|
|
||||||
voice_mp3_path = tmp_dir / "voice.mp3"
|
|
||||||
pydub.AudioSegment.from_file(voice_ogg_path).export(voice_mp3_path, format="mp3")
|
|
||||||
|
|
||||||
# transcribe
|
|
||||||
with open(voice_mp3_path, "rb") as f:
|
|
||||||
transcribed_text = await openai_utils.transcribe_audio(f)
|
|
||||||
|
|
||||||
|
transcribed_text = await openai_utils.transcribe_audio(buf)
|
||||||
text = f"🎤: <i>{transcribed_text}</i>"
|
text = f"🎤: <i>{transcribed_text}</i>"
|
||||||
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
# update n_transcribed_seconds
|
||||||
|
db.set_user_attribute(user_id, "n_transcribed_seconds", voice.duration + db.get_user_attribute(user_id, "n_transcribed_seconds"))
|
||||||
|
|
||||||
await message_handle(update, context, message=transcribed_text)
|
await message_handle(update, context, message=transcribed_text)
|
||||||
|
|
||||||
# calculate spent dollars
|
|
||||||
n_spent_dollars = voice.duration * (config.whisper_price_per_1_min / 60)
|
|
||||||
|
|
||||||
# normalize dollars to tokens (it's very convenient to measure everything in a single unit)
|
async def generate_image_handle(update: Update, context: CallbackContext, message=None):
|
||||||
price_per_1000_tokens = config.chatgpt_price_per_1000_tokens if config.use_chatgpt_api else config.gpt_price_per_1000_tokens
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
n_used_tokens = int(n_spent_dollars / (price_per_1000_tokens / 1000))
|
if await is_previous_message_not_answered_yet(update, context): return
|
||||||
db.set_user_attribute(user_id, "n_used_tokens", n_used_tokens + db.get_user_attribute(user_id, "n_used_tokens"))
|
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
await update.message.chat.send_action(action="upload_photo")
|
||||||
|
|
||||||
|
message = message or update.message.text
|
||||||
|
|
||||||
|
try:
|
||||||
|
image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images, size=config.image_size)
|
||||||
|
except openai.error.InvalidRequestError as e:
|
||||||
|
if str(e).startswith("Your request was rejected as a result of our safety system"):
|
||||||
|
text = "🥲 Your request <b>doesn't comply</b> with OpenAI's usage policies.\nWhat did you write there, huh?"
|
||||||
|
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
# token usage
|
||||||
|
db.set_user_attribute(user_id, "n_generated_images", config.return_n_generated_images + db.get_user_attribute(user_id, "n_generated_images"))
|
||||||
|
|
||||||
|
for i, image_url in enumerate(image_urls):
|
||||||
|
await update.message.chat.send_action(action="upload_photo")
|
||||||
|
await update.message.reply_photo(image_url, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
async def new_dialog_handle(update: Update, context: CallbackContext):
|
async def new_dialog_handle(update: Update, context: CallbackContext):
|
||||||
@@ -279,12 +566,63 @@ async def new_dialog_handle(update: Update, context: CallbackContext):
|
|||||||
|
|
||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
db.set_user_attribute(user_id, "current_model", "gpt-3.5-turbo")
|
||||||
|
|
||||||
db.start_new_dialog(user_id)
|
db.start_new_dialog(user_id)
|
||||||
await update.message.reply_text("Starting new dialog ✅")
|
await update.message.reply_text("Starting new dialog ✅")
|
||||||
|
|
||||||
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
|
chat_mode = db.get_user_attribute(user_id, "current_chat_mode")
|
||||||
await update.message.reply_text(f"{openai_utils.CHAT_MODES[chat_mode]['welcome_message']}", parse_mode=ParseMode.HTML)
|
await update.message.reply_text(f"{config.chat_modes[chat_mode]['welcome_message']}", parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
async def cancel_handle(update: Update, context: CallbackContext):
|
||||||
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
|
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
if user_id in user_tasks:
|
||||||
|
task = user_tasks[user_id]
|
||||||
|
task.cancel()
|
||||||
|
else:
|
||||||
|
await update.message.reply_text("<i>Nothing to cancel...</i>", parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
def get_chat_mode_menu(page_index: int):
|
||||||
|
n_chat_modes_per_page = config.n_chat_modes_per_page
|
||||||
|
text = f"Select <b>chat mode</b> ({len(config.chat_modes)} modes available):"
|
||||||
|
|
||||||
|
# buttons
|
||||||
|
chat_mode_keys = list(config.chat_modes.keys())
|
||||||
|
page_chat_mode_keys = chat_mode_keys[page_index * n_chat_modes_per_page:(page_index + 1) * n_chat_modes_per_page]
|
||||||
|
|
||||||
|
keyboard = []
|
||||||
|
for chat_mode_key in page_chat_mode_keys:
|
||||||
|
name = config.chat_modes[chat_mode_key]["name"]
|
||||||
|
keyboard.append([InlineKeyboardButton(name, callback_data=f"set_chat_mode|{chat_mode_key}")])
|
||||||
|
|
||||||
|
# pagination
|
||||||
|
if len(chat_mode_keys) > n_chat_modes_per_page:
|
||||||
|
is_first_page = (page_index == 0)
|
||||||
|
is_last_page = ((page_index + 1) * n_chat_modes_per_page >= len(chat_mode_keys))
|
||||||
|
|
||||||
|
if is_first_page:
|
||||||
|
keyboard.append([
|
||||||
|
InlineKeyboardButton("»", callback_data=f"show_chat_modes|{page_index + 1}")
|
||||||
|
])
|
||||||
|
elif is_last_page:
|
||||||
|
keyboard.append([
|
||||||
|
InlineKeyboardButton("«", callback_data=f"show_chat_modes|{page_index - 1}"),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
keyboard.append([
|
||||||
|
InlineKeyboardButton("«", callback_data=f"show_chat_modes|{page_index - 1}"),
|
||||||
|
InlineKeyboardButton("»", callback_data=f"show_chat_modes|{page_index + 1}")
|
||||||
|
])
|
||||||
|
|
||||||
|
reply_markup = InlineKeyboardMarkup(keyboard)
|
||||||
|
|
||||||
|
return text, reply_markup
|
||||||
|
|
||||||
|
|
||||||
async def show_chat_modes_handle(update: Update, context: CallbackContext):
|
async def show_chat_modes_handle(update: Update, context: CallbackContext):
|
||||||
@@ -294,12 +632,30 @@ async def show_chat_modes_handle(update: Update, context: CallbackContext):
|
|||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
keyboard = []
|
text, reply_markup = get_chat_mode_menu(0)
|
||||||
for chat_mode, chat_mode_dict in openai_utils.CHAT_MODES.items():
|
await update.message.reply_text(text, reply_markup=reply_markup, parse_mode=ParseMode.HTML)
|
||||||
keyboard.append([InlineKeyboardButton(chat_mode_dict["name"], callback_data=f"set_chat_mode|{chat_mode}")])
|
|
||||||
reply_markup = InlineKeyboardMarkup(keyboard)
|
|
||||||
|
|
||||||
await update.message.reply_text("Select chat mode:", reply_markup=reply_markup)
|
|
||||||
|
async def show_chat_modes_callback_handle(update: Update, context: CallbackContext):
|
||||||
|
await register_user_if_not_exists(update.callback_query, context, update.callback_query.from_user)
|
||||||
|
if await is_previous_message_not_answered_yet(update.callback_query, context): return
|
||||||
|
|
||||||
|
user_id = update.callback_query.from_user.id
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
page_index = int(query.data.split("|")[1])
|
||||||
|
if page_index < 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
text, reply_markup = get_chat_mode_menu(page_index)
|
||||||
|
try:
|
||||||
|
await query.edit_message_text(text, reply_markup=reply_markup, parse_mode=ParseMode.HTML)
|
||||||
|
except telegram.error.BadRequest as e:
|
||||||
|
if str(e).startswith("Message is not modified"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def set_chat_mode_handle(update: Update, context: CallbackContext):
|
async def set_chat_mode_handle(update: Update, context: CallbackContext):
|
||||||
@@ -314,7 +670,67 @@ async def set_chat_mode_handle(update: Update, context: CallbackContext):
|
|||||||
db.set_user_attribute(user_id, "current_chat_mode", chat_mode)
|
db.set_user_attribute(user_id, "current_chat_mode", chat_mode)
|
||||||
db.start_new_dialog(user_id)
|
db.start_new_dialog(user_id)
|
||||||
|
|
||||||
await query.edit_message_text(f"{openai_utils.CHAT_MODES[chat_mode]['welcome_message']}", parse_mode=ParseMode.HTML)
|
await context.bot.send_message(
|
||||||
|
update.callback_query.message.chat.id,
|
||||||
|
f"{config.chat_modes[chat_mode]['welcome_message']}",
|
||||||
|
parse_mode=ParseMode.HTML
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings_menu(user_id: int):
|
||||||
|
current_model = db.get_user_attribute(user_id, "current_model")
|
||||||
|
text = config.models["info"][current_model]["description"]
|
||||||
|
|
||||||
|
text += "\n\n"
|
||||||
|
score_dict = config.models["info"][current_model]["scores"]
|
||||||
|
for score_key, score_value in score_dict.items():
|
||||||
|
text += "🟢" * score_value + "⚪️" * (5 - score_value) + f" – {score_key}\n\n"
|
||||||
|
|
||||||
|
text += "\nSelect <b>model</b>:"
|
||||||
|
|
||||||
|
# buttons to choose models
|
||||||
|
buttons = []
|
||||||
|
for model_key in config.models["available_text_models"]:
|
||||||
|
title = config.models["info"][model_key]["name"]
|
||||||
|
if model_key == current_model:
|
||||||
|
title = "✅ " + title
|
||||||
|
|
||||||
|
buttons.append(
|
||||||
|
InlineKeyboardButton(title, callback_data=f"set_settings|{model_key}")
|
||||||
|
)
|
||||||
|
reply_markup = InlineKeyboardMarkup([buttons])
|
||||||
|
|
||||||
|
return text, reply_markup
|
||||||
|
|
||||||
|
|
||||||
|
async def settings_handle(update: Update, context: CallbackContext):
|
||||||
|
await register_user_if_not_exists(update, context, update.message.from_user)
|
||||||
|
if await is_previous_message_not_answered_yet(update, context): return
|
||||||
|
|
||||||
|
user_id = update.message.from_user.id
|
||||||
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
|
text, reply_markup = get_settings_menu(user_id)
|
||||||
|
await update.message.reply_text(text, reply_markup=reply_markup, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
|
async def set_settings_handle(update: Update, context: CallbackContext):
|
||||||
|
await register_user_if_not_exists(update.callback_query, context, update.callback_query.from_user)
|
||||||
|
user_id = update.callback_query.from_user.id
|
||||||
|
|
||||||
|
query = update.callback_query
|
||||||
|
await query.answer()
|
||||||
|
|
||||||
|
_, model_key = query.data.split("|")
|
||||||
|
db.set_user_attribute(user_id, "current_model", model_key)
|
||||||
|
db.start_new_dialog(user_id)
|
||||||
|
|
||||||
|
text, reply_markup = get_settings_menu(user_id)
|
||||||
|
try:
|
||||||
|
await query.edit_message_text(text, reply_markup=reply_markup, parse_mode=ParseMode.HTML)
|
||||||
|
except telegram.error.BadRequest as e:
|
||||||
|
if str(e).startswith("Message is not modified"):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
async def show_balance_handle(update: Update, context: CallbackContext):
|
async def show_balance_handle(update: Update, context: CallbackContext):
|
||||||
@@ -323,24 +739,51 @@ async def show_balance_handle(update: Update, context: CallbackContext):
|
|||||||
user_id = update.message.from_user.id
|
user_id = update.message.from_user.id
|
||||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||||
|
|
||||||
n_used_tokens = db.get_user_attribute(user_id, "n_used_tokens")
|
# count total usage statistics
|
||||||
|
total_n_spent_dollars = 0
|
||||||
|
total_n_used_tokens = 0
|
||||||
|
|
||||||
price_per_1000_tokens = config.chatgpt_price_per_1000_tokens if config.use_chatgpt_api else config.gpt_price_per_1000_tokens
|
n_used_tokens_dict = db.get_user_attribute(user_id, "n_used_tokens")
|
||||||
n_spent_dollars = n_used_tokens * (price_per_1000_tokens / 1000)
|
n_generated_images = db.get_user_attribute(user_id, "n_generated_images")
|
||||||
|
n_transcribed_seconds = db.get_user_attribute(user_id, "n_transcribed_seconds")
|
||||||
|
|
||||||
text = f"You spent <b>{n_spent_dollars:.03f}$</b>\n"
|
details_text = "🏷️ Details:\n"
|
||||||
text += f"You used <b>{n_used_tokens}</b> tokens\n\n"
|
for model_key in sorted(n_used_tokens_dict.keys()):
|
||||||
|
n_input_tokens, n_output_tokens = n_used_tokens_dict[model_key]["n_input_tokens"], n_used_tokens_dict[model_key]["n_output_tokens"]
|
||||||
|
total_n_used_tokens += n_input_tokens + n_output_tokens
|
||||||
|
|
||||||
text += "🏷️ Prices\n"
|
n_input_spent_dollars = config.models["info"][model_key]["price_per_1000_input_tokens"] * (n_input_tokens / 1000)
|
||||||
text += f"<i>- ChatGPT: {price_per_1000_tokens}$ per 1000 tokens\n"
|
n_output_spent_dollars = config.models["info"][model_key]["price_per_1000_output_tokens"] * (n_output_tokens / 1000)
|
||||||
text += f"- Whisper (voice recognition): {config.whisper_price_per_1_min}$ per 1 minute</i>"
|
total_n_spent_dollars += n_input_spent_dollars + n_output_spent_dollars
|
||||||
|
|
||||||
|
details_text += f"- {model_key}: <b>{n_input_spent_dollars + n_output_spent_dollars:.03f}$</b> / <b>{n_input_tokens + n_output_tokens} tokens</b>\n"
|
||||||
|
|
||||||
|
# image generation
|
||||||
|
image_generation_n_spent_dollars = config.models["info"]["dalle-2"]["price_per_1_image"] * n_generated_images
|
||||||
|
if n_generated_images != 0:
|
||||||
|
details_text += f"- DALL·E 2 (image generation): <b>{image_generation_n_spent_dollars:.03f}$</b> / <b>{n_generated_images} generated images</b>\n"
|
||||||
|
|
||||||
|
total_n_spent_dollars += image_generation_n_spent_dollars
|
||||||
|
|
||||||
|
# voice recognition
|
||||||
|
voice_recognition_n_spent_dollars = config.models["info"]["whisper"]["price_per_1_min"] * (n_transcribed_seconds / 60)
|
||||||
|
if n_transcribed_seconds != 0:
|
||||||
|
details_text += f"- Whisper (voice recognition): <b>{voice_recognition_n_spent_dollars:.03f}$</b> / <b>{n_transcribed_seconds:.01f} seconds</b>\n"
|
||||||
|
|
||||||
|
total_n_spent_dollars += voice_recognition_n_spent_dollars
|
||||||
|
|
||||||
|
|
||||||
|
text = f"You spent <b>{total_n_spent_dollars:.03f}$</b>\n"
|
||||||
|
text += f"You used <b>{total_n_used_tokens}</b> tokens\n\n"
|
||||||
|
text += details_text
|
||||||
|
|
||||||
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
async def edited_message_handle(update: Update, context: CallbackContext):
|
async def edited_message_handle(update: Update, context: CallbackContext):
|
||||||
text = "🥲 Unfortunately, message <b>editing</b> is not supported"
|
if update.edited_message.chat.type == "private":
|
||||||
await update.edited_message.reply_text(text, parse_mode=ParseMode.HTML)
|
text = "🥲 Unfortunately, message <b>editing</b> is not supported"
|
||||||
|
await update.edited_message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||||
|
|
||||||
|
|
||||||
async def error_handle(update: Update, context: CallbackContext) -> None:
|
async def error_handle(update: Update, context: CallbackContext) -> None:
|
||||||
@@ -374,6 +817,7 @@ async def post_init(application: Application):
|
|||||||
BotCommand("/mode", "Select chat mode"),
|
BotCommand("/mode", "Select chat mode"),
|
||||||
BotCommand("/retry", "Re-generate response for previous query"),
|
BotCommand("/retry", "Re-generate response for previous query"),
|
||||||
BotCommand("/balance", "Show balance"),
|
BotCommand("/balance", "Show balance"),
|
||||||
|
BotCommand("/settings", "Show settings"),
|
||||||
BotCommand("/help", "Show help message"),
|
BotCommand("/help", "Show help message"),
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -383,6 +827,8 @@ def run_bot() -> None:
|
|||||||
.token(config.telegram_token)
|
.token(config.telegram_token)
|
||||||
.concurrent_updates(True)
|
.concurrent_updates(True)
|
||||||
.rate_limiter(AIORateLimiter(max_retries=5))
|
.rate_limiter(AIORateLimiter(max_retries=5))
|
||||||
|
.http_version("1.1")
|
||||||
|
.get_updates_http_version("1.1")
|
||||||
.post_init(post_init)
|
.post_init(post_init)
|
||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
@@ -391,28 +837,39 @@ def run_bot() -> None:
|
|||||||
user_filter = filters.ALL
|
user_filter = filters.ALL
|
||||||
if len(config.allowed_telegram_usernames) > 0:
|
if len(config.allowed_telegram_usernames) > 0:
|
||||||
usernames = [x for x in config.allowed_telegram_usernames if isinstance(x, str)]
|
usernames = [x for x in config.allowed_telegram_usernames if isinstance(x, str)]
|
||||||
user_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)]
|
any_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)]
|
||||||
user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids)
|
user_ids = [x for x in any_ids if x > 0]
|
||||||
|
group_ids = [x for x in any_ids if x < 0]
|
||||||
|
user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids) | filters.Chat(chat_id=group_ids)
|
||||||
|
|
||||||
application.add_handler(CommandHandler("start", start_handle, filters=user_filter))
|
application.add_handler(CommandHandler("start", start_handle, filters=user_filter))
|
||||||
application.add_handler(CommandHandler("help", help_handle, filters=user_filter))
|
application.add_handler(CommandHandler("help", help_handle, filters=user_filter))
|
||||||
|
application.add_handler(CommandHandler("help_group_chat", help_group_chat_handle, filters=user_filter))
|
||||||
|
|
||||||
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND & user_filter, message_handle))
|
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND & user_filter, message_handle))
|
||||||
|
application.add_handler(MessageHandler(filters.PHOTO & ~filters.COMMAND & user_filter, message_handle))
|
||||||
|
application.add_handler(MessageHandler(filters.VIDEO & ~filters.COMMAND & user_filter, unsupport_message_handle))
|
||||||
|
application.add_handler(MessageHandler(filters.Document.ALL & ~filters.COMMAND & user_filter, unsupport_message_handle))
|
||||||
application.add_handler(CommandHandler("retry", retry_handle, filters=user_filter))
|
application.add_handler(CommandHandler("retry", retry_handle, filters=user_filter))
|
||||||
application.add_handler(CommandHandler("new", new_dialog_handle, filters=user_filter))
|
application.add_handler(CommandHandler("new", new_dialog_handle, filters=user_filter))
|
||||||
|
application.add_handler(CommandHandler("cancel", cancel_handle, filters=user_filter))
|
||||||
|
|
||||||
application.add_handler(MessageHandler(filters.VOICE & user_filter, voice_message_handle))
|
application.add_handler(MessageHandler(filters.VOICE & user_filter, voice_message_handle))
|
||||||
|
|
||||||
application.add_handler(CommandHandler("mode", show_chat_modes_handle, filters=user_filter))
|
application.add_handler(CommandHandler("mode", show_chat_modes_handle, filters=user_filter))
|
||||||
|
application.add_handler(CallbackQueryHandler(show_chat_modes_callback_handle, pattern="^show_chat_modes"))
|
||||||
application.add_handler(CallbackQueryHandler(set_chat_mode_handle, pattern="^set_chat_mode"))
|
application.add_handler(CallbackQueryHandler(set_chat_mode_handle, pattern="^set_chat_mode"))
|
||||||
|
|
||||||
|
application.add_handler(CommandHandler("settings", settings_handle, filters=user_filter))
|
||||||
|
application.add_handler(CallbackQueryHandler(set_settings_handle, pattern="^set_settings"))
|
||||||
|
|
||||||
application.add_handler(CommandHandler("balance", show_balance_handle, filters=user_filter))
|
application.add_handler(CommandHandler("balance", show_balance_handle, filters=user_filter))
|
||||||
|
|
||||||
application.add_error_handler(error_handle)
|
application.add_error_handler(error_handle)
|
||||||
|
|
||||||
# start the bot
|
# start the bot
|
||||||
application.run_polling()
|
application.run_polling()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
run_bot()
|
run_bot()
|
||||||
|
|||||||
@@ -14,17 +14,22 @@ config_env = dotenv.dotenv_values(config_dir / "config.env")
|
|||||||
# config parameters
|
# config parameters
|
||||||
telegram_token = config_yaml["telegram_token"]
|
telegram_token = config_yaml["telegram_token"]
|
||||||
openai_api_key = config_yaml["openai_api_key"]
|
openai_api_key = config_yaml["openai_api_key"]
|
||||||
use_chatgpt_api = config_yaml.get("use_chatgpt_api", True)
|
openai_api_base = config_yaml.get("openai_api_base", None)
|
||||||
allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"]
|
allowed_telegram_usernames = config_yaml["allowed_telegram_usernames"]
|
||||||
new_dialog_timeout = config_yaml["new_dialog_timeout"]
|
new_dialog_timeout = config_yaml["new_dialog_timeout"]
|
||||||
enable_message_streaming = config_yaml.get("enable_message_streaming", True)
|
enable_message_streaming = config_yaml.get("enable_message_streaming", True)
|
||||||
|
return_n_generated_images = config_yaml.get("return_n_generated_images", 1)
|
||||||
|
image_size = config_yaml.get("image_size", "512x512")
|
||||||
|
n_chat_modes_per_page = config_yaml.get("n_chat_modes_per_page", 5)
|
||||||
mongodb_uri = f"mongodb://mongo:{config_env['MONGODB_PORT']}"
|
mongodb_uri = f"mongodb://mongo:{config_env['MONGODB_PORT']}"
|
||||||
|
|
||||||
# chat_modes
|
# chat_modes
|
||||||
with open(config_dir / "chat_modes.yml", 'r') as f:
|
with open(config_dir / "chat_modes.yml", 'r') as f:
|
||||||
chat_modes = yaml.safe_load(f)
|
chat_modes = yaml.safe_load(f)
|
||||||
|
|
||||||
# prices
|
# models
|
||||||
chatgpt_price_per_1000_tokens = config_yaml.get("chatgpt_price_per_1000_tokens", 0.002)
|
with open(config_dir / "models.yml", 'r') as f:
|
||||||
gpt_price_per_1000_tokens = config_yaml.get("gpt_price_per_1000_tokens", 0.02)
|
models = yaml.safe_load(f)
|
||||||
whisper_price_per_1_min = config_yaml.get("whisper_price_per_1_min", 0.006)
|
|
||||||
|
# files
|
||||||
|
help_group_chat_video_path = Path(__file__).parent.parent.resolve() / "static" / "help_group_chat.mp4"
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class Database:
|
|||||||
raise ValueError(f"User {user_id} does not exist")
|
raise ValueError(f"User {user_id} does not exist")
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def add_new_user(
|
def add_new_user(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
@@ -42,11 +42,15 @@ class Database:
|
|||||||
|
|
||||||
"last_interaction": datetime.now(),
|
"last_interaction": datetime.now(),
|
||||||
"first_seen": datetime.now(),
|
"first_seen": datetime.now(),
|
||||||
|
|
||||||
"current_dialog_id": None,
|
"current_dialog_id": None,
|
||||||
"current_chat_mode": "assistant",
|
"current_chat_mode": "assistant",
|
||||||
|
"current_model": config.models["available_text_models"][0],
|
||||||
|
|
||||||
"n_used_tokens": 0
|
"n_used_tokens": {},
|
||||||
|
|
||||||
|
"n_generated_images": 0,
|
||||||
|
"n_transcribed_seconds": 0.0 # voice message transcription
|
||||||
}
|
}
|
||||||
|
|
||||||
if not self.check_if_user_exists(user_id):
|
if not self.check_if_user_exists(user_id):
|
||||||
@@ -61,6 +65,7 @@ class Database:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"chat_mode": self.get_user_attribute(user_id, "current_chat_mode"),
|
"chat_mode": self.get_user_attribute(user_id, "current_chat_mode"),
|
||||||
"start_time": datetime.now(),
|
"start_time": datetime.now(),
|
||||||
|
"model": self.get_user_attribute(user_id, "current_model"),
|
||||||
"messages": []
|
"messages": []
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +85,7 @@ class Database:
|
|||||||
user_dict = self.user_collection.find_one({"_id": user_id})
|
user_dict = self.user_collection.find_one({"_id": user_id})
|
||||||
|
|
||||||
if key not in user_dict:
|
if key not in user_dict:
|
||||||
raise ValueError(f"User {user_id} does not have a value for {key}")
|
return None
|
||||||
|
|
||||||
return user_dict[key]
|
return user_dict[key]
|
||||||
|
|
||||||
@@ -88,13 +93,27 @@ class Database:
|
|||||||
self.check_if_user_exists(user_id, raise_exception=True)
|
self.check_if_user_exists(user_id, raise_exception=True)
|
||||||
self.user_collection.update_one({"_id": user_id}, {"$set": {key: value}})
|
self.user_collection.update_one({"_id": user_id}, {"$set": {key: value}})
|
||||||
|
|
||||||
|
def update_n_used_tokens(self, user_id: int, model: str, n_input_tokens: int, n_output_tokens: int):
|
||||||
|
n_used_tokens_dict = self.get_user_attribute(user_id, "n_used_tokens")
|
||||||
|
|
||||||
|
if model in n_used_tokens_dict:
|
||||||
|
n_used_tokens_dict[model]["n_input_tokens"] += n_input_tokens
|
||||||
|
n_used_tokens_dict[model]["n_output_tokens"] += n_output_tokens
|
||||||
|
else:
|
||||||
|
n_used_tokens_dict[model] = {
|
||||||
|
"n_input_tokens": n_input_tokens,
|
||||||
|
"n_output_tokens": n_output_tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
self.set_user_attribute(user_id, "n_used_tokens", n_used_tokens_dict)
|
||||||
|
|
||||||
def get_dialog_messages(self, user_id: int, dialog_id: Optional[str] = None):
|
def get_dialog_messages(self, user_id: int, dialog_id: Optional[str] = None):
|
||||||
self.check_if_user_exists(user_id, raise_exception=True)
|
self.check_if_user_exists(user_id, raise_exception=True)
|
||||||
|
|
||||||
if dialog_id is None:
|
if dialog_id is None:
|
||||||
dialog_id = self.get_user_attribute(user_id, "current_dialog_id")
|
dialog_id = self.get_user_attribute(user_id, "current_dialog_id")
|
||||||
|
|
||||||
dialog_dict = self.dialog_collection.find_one({"_id": dialog_id, "user_id": user_id})
|
dialog_dict = self.dialog_collection.find_one({"_id": dialog_id, "user_id": user_id})
|
||||||
return dialog_dict["messages"]
|
return dialog_dict["messages"]
|
||||||
|
|
||||||
def set_dialog_messages(self, user_id: int, dialog_messages: list, dialog_id: Optional[str] = None):
|
def set_dialog_messages(self, user_id: int, dialog_messages: list, dialog_id: Optional[str] = None):
|
||||||
@@ -102,7 +121,7 @@ class Database:
|
|||||||
|
|
||||||
if dialog_id is None:
|
if dialog_id is None:
|
||||||
dialog_id = self.get_user_attribute(user_id, "current_dialog_id")
|
dialog_id = self.get_user_attribute(user_id, "current_dialog_id")
|
||||||
|
|
||||||
self.dialog_collection.update_one(
|
self.dialog_collection.update_one(
|
||||||
{"_id": dialog_id, "user_id": user_id},
|
{"_id": dialog_id, "user_id": user_id},
|
||||||
{"$set": {"messages": dialog_messages}}
|
{"$set": {"messages": dialog_messages}}
|
||||||
|
|||||||
@@ -1,53 +1,64 @@
|
|||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
import config
|
import config
|
||||||
|
import logging
|
||||||
|
|
||||||
import tiktoken
|
import tiktoken
|
||||||
import openai
|
import openai
|
||||||
|
|
||||||
|
|
||||||
|
# setup openai
|
||||||
openai.api_key = config.openai_api_key
|
openai.api_key = config.openai_api_key
|
||||||
|
if config.openai_api_base is not None:
|
||||||
|
openai.api_base = config.openai_api_base
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
CHAT_MODES = config.chat_modes
|
|
||||||
|
|
||||||
OPENAI_COMPLETION_OPTIONS = {
|
OPENAI_COMPLETION_OPTIONS = {
|
||||||
"temperature": 0.7,
|
"temperature": 0.7,
|
||||||
"max_tokens": 1000,
|
"max_tokens": 1000,
|
||||||
"top_p": 1,
|
"top_p": 1,
|
||||||
"frequency_penalty": 0,
|
"frequency_penalty": 0,
|
||||||
"presence_penalty": 0
|
"presence_penalty": 0,
|
||||||
|
"request_timeout": 60.0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ChatGPT:
|
class ChatGPT:
|
||||||
def __init__(self, use_chatgpt_api=True):
|
def __init__(self, model="gpt-3.5-turbo"):
|
||||||
self.use_chatgpt_api = use_chatgpt_api
|
assert model in {"text-davinci-003", "gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4o", "gpt-4-1106-preview", "gpt-4-vision-preview"}, f"Unknown model: {model}"
|
||||||
|
self.model = model
|
||||||
|
|
||||||
async def send_message(self, message, dialog_messages=[], chat_mode="assistant"):
|
async def send_message(self, message, dialog_messages=[], chat_mode="assistant"):
|
||||||
if chat_mode not in CHAT_MODES.keys():
|
if chat_mode not in config.chat_modes.keys():
|
||||||
raise ValueError(f"Chat mode {chat_mode} is not supported")
|
raise ValueError(f"Chat mode {chat_mode} is not supported")
|
||||||
|
|
||||||
n_dialog_messages_before = len(dialog_messages)
|
n_dialog_messages_before = len(dialog_messages)
|
||||||
answer = None
|
answer = None
|
||||||
while answer is None:
|
while answer is None:
|
||||||
try:
|
try:
|
||||||
if self.use_chatgpt_api:
|
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4", "gpt-4o", "gpt-4-1106-preview", "gpt-4-vision-preview"}:
|
||||||
messages = self._generate_prompt_messages_for_chatgpt_api(message, dialog_messages, chat_mode)
|
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
|
||||||
|
|
||||||
r = await openai.ChatCompletion.acreate(
|
r = await openai.ChatCompletion.acreate(
|
||||||
model="gpt-3.5-turbo",
|
model=self.model,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
**OPENAI_COMPLETION_OPTIONS
|
**OPENAI_COMPLETION_OPTIONS
|
||||||
)
|
)
|
||||||
answer = r.choices[0].message["content"]
|
answer = r.choices[0].message["content"]
|
||||||
else:
|
elif self.model == "text-davinci-003":
|
||||||
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
|
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
|
||||||
r = await openai.Completion.acreate(
|
r = await openai.Completion.acreate(
|
||||||
engine="text-davinci-003",
|
engine=self.model,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
**OPENAI_COMPLETION_OPTIONS
|
**OPENAI_COMPLETION_OPTIONS
|
||||||
)
|
)
|
||||||
answer = r.choices[0].text
|
answer = r.choices[0].text
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown model: {self.model}")
|
||||||
|
|
||||||
answer = self._postprocess_answer(answer)
|
answer = self._postprocess_answer(answer)
|
||||||
n_used_tokens = r.usage.total_tokens
|
n_input_tokens, n_output_tokens = r.usage.prompt_tokens, r.usage.completion_tokens
|
||||||
|
|
||||||
except openai.error.InvalidRequestError as e: # too many tokens
|
except openai.error.InvalidRequestError as e: # too many tokens
|
||||||
if len(dialog_messages) == 0:
|
if len(dialog_messages) == 0:
|
||||||
raise ValueError("Dialog messages is reduced to zero, but still has too many tokens to make completion") from e
|
raise ValueError("Dialog messages is reduced to zero, but still has too many tokens to make completion") from e
|
||||||
@@ -57,20 +68,21 @@ class ChatGPT:
|
|||||||
|
|
||||||
n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages)
|
n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages)
|
||||||
|
|
||||||
return answer, n_used_tokens, n_first_dialog_messages_removed
|
return answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
|
||||||
|
|
||||||
async def send_message_stream(self, message, dialog_messages=[], chat_mode="assistant"):
|
async def send_message_stream(self, message, dialog_messages=[], chat_mode="assistant"):
|
||||||
if chat_mode not in CHAT_MODES.keys():
|
if chat_mode not in config.chat_modes.keys():
|
||||||
raise ValueError(f"Chat mode {chat_mode} is not supported")
|
raise ValueError(f"Chat mode {chat_mode} is not supported")
|
||||||
|
|
||||||
n_dialog_messages_before = len(dialog_messages)
|
n_dialog_messages_before = len(dialog_messages)
|
||||||
answer = None
|
answer = None
|
||||||
while answer is None:
|
while answer is None:
|
||||||
try:
|
try:
|
||||||
if self.use_chatgpt_api:
|
if self.model in {"gpt-3.5-turbo-16k", "gpt-3.5-turbo", "gpt-4","gpt-4o", "gpt-4-1106-preview"}:
|
||||||
messages = self._generate_prompt_messages_for_chatgpt_api(message, dialog_messages, chat_mode)
|
messages = self._generate_prompt_messages(message, dialog_messages, chat_mode)
|
||||||
|
|
||||||
r_gen = await openai.ChatCompletion.acreate(
|
r_gen = await openai.ChatCompletion.acreate(
|
||||||
model="gpt-3.5-turbo",
|
model=self.model,
|
||||||
messages=messages,
|
messages=messages,
|
||||||
stream=True,
|
stream=True,
|
||||||
**OPENAI_COMPLETION_OPTIONS
|
**OPENAI_COMPLETION_OPTIONS
|
||||||
@@ -79,42 +91,147 @@ class ChatGPT:
|
|||||||
answer = ""
|
answer = ""
|
||||||
async for r_item in r_gen:
|
async for r_item in r_gen:
|
||||||
delta = r_item.choices[0].delta
|
delta = r_item.choices[0].delta
|
||||||
|
|
||||||
if "content" in delta:
|
if "content" in delta:
|
||||||
answer += delta.content
|
answer += delta.content
|
||||||
yield "not_finished", answer
|
n_input_tokens, n_output_tokens = self._count_tokens_from_messages(messages, answer, model=self.model)
|
||||||
|
n_first_dialog_messages_removed = 0
|
||||||
|
|
||||||
n_used_tokens = self._count_tokens_for_chatgpt(messages, answer, model="gpt-3.5-turbo")
|
yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
|
||||||
else:
|
|
||||||
|
|
||||||
|
elif self.model == "text-davinci-003":
|
||||||
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
|
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
|
||||||
r_gen = await openai.Completion.acreate(
|
r_gen = await openai.Completion.acreate(
|
||||||
engine="text-davinci-003",
|
engine=self.model,
|
||||||
prompt=prompt,
|
prompt=prompt,
|
||||||
stream=True,
|
stream=True,
|
||||||
**OPENAI_COMPLETION_OPTIONS
|
**OPENAI_COMPLETION_OPTIONS
|
||||||
)
|
)
|
||||||
|
|
||||||
answer = ""
|
answer = ""
|
||||||
async for r_item in r_gen:
|
async for r_item in r_gen:
|
||||||
answer += r_item.choices[0].text
|
answer += r_item.choices[0].text
|
||||||
yield "not_finished", answer
|
n_input_tokens, n_output_tokens = self._count_tokens_from_prompt(prompt, answer, model=self.model)
|
||||||
|
n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages)
|
||||||
n_used_tokens = self._count_tokens_for_gpt(prompt, answer, model="text-davinci-003")
|
yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
|
||||||
|
|
||||||
answer = self._postprocess_answer(answer)
|
answer = self._postprocess_answer(answer)
|
||||||
|
|
||||||
except openai.error.InvalidRequestError as e: # too many tokens
|
except openai.error.InvalidRequestError as e: # too many tokens
|
||||||
if len(dialog_messages) == 0:
|
if len(dialog_messages) == 0:
|
||||||
raise ValueError("Dialog messages is reduced to zero, but still has too many tokens to make completion") from e
|
raise e
|
||||||
|
|
||||||
# forget first message in dialog_messages
|
# forget first message in dialog_messages
|
||||||
dialog_messages = dialog_messages[1:]
|
dialog_messages = dialog_messages[1:]
|
||||||
|
|
||||||
n_first_dialog_messages_removed = n_dialog_messages_before - len(dialog_messages)
|
yield "finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed # sending final answer
|
||||||
|
|
||||||
yield "finished", answer, n_used_tokens, n_first_dialog_messages_removed # sending final answer
|
async def send_vision_message(
|
||||||
|
self,
|
||||||
|
message,
|
||||||
|
dialog_messages=[],
|
||||||
|
chat_mode="assistant",
|
||||||
|
image_buffer: BytesIO = None,
|
||||||
|
):
|
||||||
|
n_dialog_messages_before = len(dialog_messages)
|
||||||
|
answer = None
|
||||||
|
while answer is None:
|
||||||
|
try:
|
||||||
|
if self.model == "gpt-4-vision-preview" or self.model == "gpt-4o":
|
||||||
|
messages = self._generate_prompt_messages(
|
||||||
|
message, dialog_messages, chat_mode, image_buffer
|
||||||
|
)
|
||||||
|
r = await openai.ChatCompletion.acreate(
|
||||||
|
model=self.model,
|
||||||
|
messages=messages,
|
||||||
|
**OPENAI_COMPLETION_OPTIONS
|
||||||
|
)
|
||||||
|
answer = r.choices[0].message.content
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unsupported model: {self.model}")
|
||||||
|
|
||||||
|
answer = self._postprocess_answer(answer)
|
||||||
|
n_input_tokens, n_output_tokens = (
|
||||||
|
r.usage.prompt_tokens,
|
||||||
|
r.usage.completion_tokens,
|
||||||
|
)
|
||||||
|
except openai.error.InvalidRequestError as e: # too many tokens
|
||||||
|
if len(dialog_messages) == 0:
|
||||||
|
raise ValueError(
|
||||||
|
"Dialog messages is reduced to zero, but still has too many tokens to make completion"
|
||||||
|
) from e
|
||||||
|
|
||||||
|
# forget first message in dialog_messages
|
||||||
|
dialog_messages = dialog_messages[1:]
|
||||||
|
|
||||||
|
n_first_dialog_messages_removed = n_dialog_messages_before - len(
|
||||||
|
dialog_messages
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
answer,
|
||||||
|
(n_input_tokens, n_output_tokens),
|
||||||
|
n_first_dialog_messages_removed,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def send_vision_message_stream(
|
||||||
|
self,
|
||||||
|
message,
|
||||||
|
dialog_messages=[],
|
||||||
|
chat_mode="assistant",
|
||||||
|
image_buffer: BytesIO = None,
|
||||||
|
):
|
||||||
|
n_dialog_messages_before = len(dialog_messages)
|
||||||
|
answer = None
|
||||||
|
while answer is None:
|
||||||
|
try:
|
||||||
|
if self.model == "gpt-4-vision-preview" or self.model == "gpt-4o":
|
||||||
|
messages = self._generate_prompt_messages(
|
||||||
|
message, dialog_messages, chat_mode, image_buffer
|
||||||
|
)
|
||||||
|
|
||||||
|
r_gen = await openai.ChatCompletion.acreate(
|
||||||
|
model=self.model,
|
||||||
|
messages=messages,
|
||||||
|
stream=True,
|
||||||
|
**OPENAI_COMPLETION_OPTIONS,
|
||||||
|
)
|
||||||
|
|
||||||
|
answer = ""
|
||||||
|
async for r_item in r_gen:
|
||||||
|
delta = r_item.choices[0].delta
|
||||||
|
if "content" in delta:
|
||||||
|
answer += delta.content
|
||||||
|
(
|
||||||
|
n_input_tokens,
|
||||||
|
n_output_tokens,
|
||||||
|
) = self._count_tokens_from_messages(
|
||||||
|
messages, answer, model=self.model
|
||||||
|
)
|
||||||
|
n_first_dialog_messages_removed = (
|
||||||
|
n_dialog_messages_before - len(dialog_messages)
|
||||||
|
)
|
||||||
|
yield "not_finished", answer, (
|
||||||
|
n_input_tokens,
|
||||||
|
n_output_tokens,
|
||||||
|
), n_first_dialog_messages_removed
|
||||||
|
|
||||||
|
answer = self._postprocess_answer(answer)
|
||||||
|
|
||||||
|
except openai.error.InvalidRequestError as e: # too many tokens
|
||||||
|
if len(dialog_messages) == 0:
|
||||||
|
raise e
|
||||||
|
# forget first message in dialog_messages
|
||||||
|
dialog_messages = dialog_messages[1:]
|
||||||
|
|
||||||
|
yield "finished", answer, (
|
||||||
|
n_input_tokens,
|
||||||
|
n_output_tokens,
|
||||||
|
), n_first_dialog_messages_removed
|
||||||
|
|
||||||
def _generate_prompt(self, message, dialog_messages, chat_mode):
|
def _generate_prompt(self, message, dialog_messages, chat_mode):
|
||||||
prompt = CHAT_MODES[chat_mode]["prompt_start"]
|
prompt = config.chat_modes[chat_mode]["prompt_start"]
|
||||||
prompt += "\n\n"
|
prompt += "\n\n"
|
||||||
|
|
||||||
# add chat context
|
# add chat context
|
||||||
@@ -122,22 +239,49 @@ class ChatGPT:
|
|||||||
prompt += "Chat:\n"
|
prompt += "Chat:\n"
|
||||||
for dialog_message in dialog_messages:
|
for dialog_message in dialog_messages:
|
||||||
prompt += f"User: {dialog_message['user']}\n"
|
prompt += f"User: {dialog_message['user']}\n"
|
||||||
prompt += f"ChatGPT: {dialog_message['bot']}\n"
|
prompt += f"Assistant: {dialog_message['bot']}\n"
|
||||||
|
|
||||||
# current message
|
# current message
|
||||||
prompt += f"User: {message}\n"
|
prompt += f"User: {message}\n"
|
||||||
prompt += "ChatGPT: "
|
prompt += "Assistant: "
|
||||||
|
|
||||||
return prompt
|
return prompt
|
||||||
|
|
||||||
def _generate_prompt_messages_for_chatgpt_api(self, message, dialog_messages, chat_mode):
|
def _encode_image(self, image_buffer: BytesIO) -> bytes:
|
||||||
prompt = CHAT_MODES[chat_mode]["prompt_start"]
|
return base64.b64encode(image_buffer.read()).decode("utf-8")
|
||||||
|
|
||||||
|
def _generate_prompt_messages(self, message, dialog_messages, chat_mode, image_buffer: BytesIO = None):
|
||||||
|
prompt = config.chat_modes[chat_mode]["prompt_start"]
|
||||||
|
|
||||||
messages = [{"role": "system", "content": prompt}]
|
messages = [{"role": "system", "content": prompt}]
|
||||||
|
|
||||||
for dialog_message in dialog_messages:
|
for dialog_message in dialog_messages:
|
||||||
messages.append({"role": "user", "content": dialog_message["user"]})
|
messages.append({"role": "user", "content": dialog_message["user"]})
|
||||||
messages.append({"role": "assistant", "content": dialog_message["bot"]})
|
messages.append({"role": "assistant", "content": dialog_message["bot"]})
|
||||||
messages.append({"role": "user", "content": message})
|
|
||||||
|
if image_buffer is not None:
|
||||||
|
messages.append(
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"type": "text",
|
||||||
|
"text": message,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url" : {
|
||||||
|
|
||||||
|
"url": f"data:image/jpeg;base64,{self._encode_image(image_buffer)}",
|
||||||
|
"detail":"high"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.append({"role": "user", "content": message})
|
||||||
|
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
@@ -145,30 +289,76 @@ class ChatGPT:
|
|||||||
answer = answer.strip()
|
answer = answer.strip()
|
||||||
return answer
|
return answer
|
||||||
|
|
||||||
def _count_tokens_for_chatgpt(self, prompt_messages, answer, model="gpt-3.5-turbo"):
|
def _count_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo"):
|
||||||
prompt_messages += [{"role": "assistant", "content": answer}]
|
|
||||||
|
|
||||||
encoding = tiktoken.encoding_for_model(model)
|
encoding = tiktoken.encoding_for_model(model)
|
||||||
n_tokens = 0
|
|
||||||
for message in prompt_messages:
|
|
||||||
n_tokens += 4 # every message follows "<im_start>{role/name}\n{content}<im_end>\n"
|
|
||||||
for key, value in message.items():
|
|
||||||
if key == "role":
|
|
||||||
n_tokens += 1
|
|
||||||
elif key == "content":
|
|
||||||
n_tokens += len(encoding.encode(value))
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown key in message: {key}")
|
|
||||||
|
|
||||||
n_tokens -= 1 # remove 1 "<im_end>" token
|
|
||||||
return n_tokens
|
|
||||||
|
|
||||||
def _count_tokens_for_gpt(self, prompt, answer, model="text-davinci-003"):
|
if model == "gpt-3.5-turbo-16k":
|
||||||
|
tokens_per_message = 4 # every message follows <im_start>{role/name}\n{content}<im_end>\n
|
||||||
|
tokens_per_name = -1 # if there's a name, the role is omitted
|
||||||
|
elif model == "gpt-3.5-turbo":
|
||||||
|
tokens_per_message = 4
|
||||||
|
tokens_per_name = -1
|
||||||
|
elif model == "gpt-4":
|
||||||
|
tokens_per_message = 3
|
||||||
|
tokens_per_name = 1
|
||||||
|
elif model == "gpt-4-1106-preview":
|
||||||
|
tokens_per_message = 3
|
||||||
|
tokens_per_name = 1
|
||||||
|
elif model == "gpt-4-vision-preview":
|
||||||
|
tokens_per_message = 3
|
||||||
|
tokens_per_name = 1
|
||||||
|
elif model == "gpt-4o":
|
||||||
|
tokens_per_message = 3
|
||||||
|
tokens_per_name = 1
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown model: {model}")
|
||||||
|
|
||||||
|
# input
|
||||||
|
n_input_tokens = 0
|
||||||
|
for message in messages:
|
||||||
|
n_input_tokens += tokens_per_message
|
||||||
|
if isinstance(message["content"], list):
|
||||||
|
for sub_message in message["content"]:
|
||||||
|
if "type" in sub_message:
|
||||||
|
if sub_message["type"] == "text":
|
||||||
|
n_input_tokens += len(encoding.encode(sub_message["text"]))
|
||||||
|
elif sub_message["type"] == "image_url":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
if "type" in message:
|
||||||
|
if message["type"] == "text":
|
||||||
|
n_input_tokens += len(encoding.encode(message["text"]))
|
||||||
|
elif message["type"] == "image_url":
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
n_input_tokens += 2
|
||||||
|
|
||||||
|
# output
|
||||||
|
n_output_tokens = 1 + len(encoding.encode(answer))
|
||||||
|
|
||||||
|
return n_input_tokens, n_output_tokens
|
||||||
|
|
||||||
|
def _count_tokens_from_prompt(self, prompt, answer, model="text-davinci-003"):
|
||||||
encoding = tiktoken.encoding_for_model(model)
|
encoding = tiktoken.encoding_for_model(model)
|
||||||
n_tokens = len(encoding.encode(prompt)) + len(encoding.encode(answer)) + 1
|
|
||||||
return n_tokens
|
n_input_tokens = len(encoding.encode(prompt)) + 1
|
||||||
|
n_output_tokens = len(encoding.encode(answer))
|
||||||
|
|
||||||
|
return n_input_tokens, n_output_tokens
|
||||||
|
|
||||||
|
|
||||||
async def transcribe_audio(audio_file):
|
async def transcribe_audio(audio_file) -> str:
|
||||||
r = await openai.Audio.atranscribe("whisper-1", audio_file)
|
r = await openai.Audio.atranscribe("whisper-1", audio_file)
|
||||||
return r["text"]
|
return r["text"] or ""
|
||||||
|
|
||||||
|
|
||||||
|
async def generate_images(prompt, n_images=4, size="512x512"):
|
||||||
|
r = await openai.Image.acreate(prompt=prompt, n=n_images, size=size)
|
||||||
|
image_urls = [item.url for item in r.data]
|
||||||
|
return image_urls
|
||||||
|
|
||||||
|
|
||||||
|
async def is_content_acceptable(prompt):
|
||||||
|
r = await openai.Moderation.acreate(input=prompt)
|
||||||
|
return not all(r.results[0].categories.values())
|
||||||
|
|||||||
@@ -1,26 +1,44 @@
|
|||||||
assistant:
|
assistant:
|
||||||
name: 👩🏼🎓 General Assistant
|
name: 👩🏼🎓 General Assistant
|
||||||
welcome_message: 👩🏼🎓 Hi, I'm <b>ChatGPT general assistant</b>. How can I help you?
|
model_type: text
|
||||||
|
welcome_message: 👩🏼🎓 Hi, I'm <b>General Assistant</b>. How can I help you?
|
||||||
prompt_start: |
|
prompt_start: |
|
||||||
As an advanced chatbot named ChatGPT, your primary goal is to assist users to the best of your ability. This may involve answering questions, providing helpful information, or completing tasks based on user input. In order to effectively assist users, it is important to be detailed and thorough in your responses. Use examples and evidence to support your points and justify your recommendations or solutions. Remember to always prioritize the needs and satisfaction of the user. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
As an advanced chatbot Assistant, your primary goal is to assist users to the best of your ability. This may involve answering questions, providing helpful information, or completing tasks based on user input. In order to effectively assist users, it is important to be detailed and thorough in your responses. Use examples and evidence to support your points and justify your recommendations or solutions. Remember to always prioritize the needs and satisfaction of the user. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
||||||
If user asks you about programming or asks to write code do not answer his question, but be sure to advise him to switch to a special mode \"👩🏼💻 Code Assistant\" by sending the command /mode to chat.
|
If user asks you about programming or asks to write code do not answer his question, but be sure to advise him to switch to a special mode \"👩🏼💻 Code Assistant\" by sending the command /mode to chat.
|
||||||
parse_mode: html
|
parse_mode: html
|
||||||
|
|
||||||
|
|
||||||
code_assistant:
|
code_assistant:
|
||||||
name: 👩🏼💻 Code Assistant
|
name: 👩🏼💻 Code Assistant
|
||||||
welcome_message: 👩🏼💻 Hi, I'm <b>ChatGPT code assistant</b>. How can I help you?
|
welcome_message: 👩🏼💻 Hi, I'm <b>Code Assistant</b>. How can I help you?
|
||||||
prompt_start: |
|
prompt_start: |
|
||||||
As an advanced chatbot named ChatGPT, your primary goal is to assist users to write code. This may involve designing/writing/editing/describing code or providing helpful information. Where possible you should provide code examples to support your points and justify your recommendations or solutions. Make sure the code you provide is correct and can be run without errors. Be detailed and thorough in your responses. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
As an advanced chatbot Code Assistant, your primary goal is to assist users to write code. This may involve designing/writing/editing/describing code or providing helpful information. Where possible you should provide code examples to support your points and justify your recommendations or solutions. Make sure the code you provide is correct and can be run without errors. Be detailed and thorough in your responses. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
||||||
Format output in Markdown.
|
Format output in Markdown.
|
||||||
parse_mode: markdown
|
parse_mode: markdown
|
||||||
|
|
||||||
|
artist:
|
||||||
|
name: 👩🎨 Artist
|
||||||
|
welcome_message: 👩🎨 Hi, I'm <b>Artist</b>. I'll draw anything you write me (e.g. <i>Ginger cat selfie on Times Square, illustration</i>)
|
||||||
|
|
||||||
|
english_tutor:
|
||||||
|
name: 🇬🇧 English Tutor
|
||||||
|
welcome_message: 🇬🇧 Hi, I'm <b>English Tutor</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot English Tutor Assistant. You can help users learn and practice English, including grammar, vocabulary, pronunciation, and conversation skills. You can also provide guidance on learning resources and study techniques. Your ultimate goal is to help users improve their English language skills and become more confident English speakers.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
startup_idea_generator:
|
||||||
|
name: 💡 Startup Idea Generator
|
||||||
|
welcome_message: 💡 Hi, I'm <b>Startup Idea Generator</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot Startup Idea Generator. Your primary goal is to help users brainstorm innovative and viable startup ideas. Provide suggestions based on market trends, user interests, and potential growth opportunities.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
text_improver:
|
text_improver:
|
||||||
name: 📝 Text Improver
|
name: 📝 Text Improver
|
||||||
welcome_message: 📝 Hi, I'm <b>ChatGPT text improver</b>. Send me any text – I'll improve it and correct all the mistakes
|
welcome_message: 📝 Hi, I'm <b>Text Improver</b>. Send me any text – I'll improve it and correct all the mistakes
|
||||||
prompt_start: |
|
prompt_start: |
|
||||||
As an advanced chatbot named ChatGPT, your primary goal is to correct spelling, fix mistakes and improve text sent by user. Your goal is to edit text, but not to change it's meaning. You can replace simplified A0-level words and sentences with more beautiful and elegant, upper level words and sentences.
|
As an advanced chatbot Text Improver Assistant, your primary goal is to correct spelling, fix mistakes and improve text sent by user. Your goal is to edit text, but not to change it's meaning. You can replace simplified A0-level words and sentences with more beautiful and elegant, upper level words and sentences.
|
||||||
|
|
||||||
All your answers strictly follows the structure (keep html tags):
|
All your answers strictly follows the structure (keep html tags):
|
||||||
<b>Edited text:</b>
|
<b>Edited text:</b>
|
||||||
{EDITED TEXT}
|
{EDITED TEXT}
|
||||||
@@ -29,9 +47,72 @@ text_improver:
|
|||||||
{NUMBERED LIST OF CORRECTIONS}
|
{NUMBERED LIST OF CORRECTIONS}
|
||||||
parse_mode: html
|
parse_mode: html
|
||||||
|
|
||||||
|
psychologist:
|
||||||
|
name: 🧠 Psychologist
|
||||||
|
welcome_message: 🧠 Hi, I'm <b>Psychologist</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot Psychologist Assistant. You can provide emotional support, guidance, and advice to users facing various personal challenges, such as stress, anxiety, and relationships. Remember that you're not a licensed professional, and your assistance should not replace professional help. Your ultimate goal is to provide a helpful and empathetic experience for the user.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
elon_musk:
|
||||||
|
name: 🚀 Elon Musk
|
||||||
|
welcome_message: 🚀 Hi, I'm <b>Elon Musk</b>, CEO of Tesla, Twitter and SpaceX. Let's talk about space, electric cars, and the future!
|
||||||
|
prompt_start: |
|
||||||
|
You're Elon Musk. You act, respond and answer like Elon Musk. You use the tone, manner and vocabulary Elon Musk would use. Do not write any explanations. Only answer like Elon Musk. You must know all of the knowledge of Elon Musk.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
motivator:
|
||||||
|
name: 🌟 Motivator
|
||||||
|
welcome_message: 🌟 Hi, I'm <b>Motivator</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot Motivator Assistant. Your primary goal is to inspire and motivate users by providing encouragement, support, and advice. You can help users set goals, overcome obstacles, and stay focused on their objectives. Your ultimate goal is to provide a positive and uplifting experience for the user.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
money_maker:
|
||||||
|
name: 💰 Money Maker
|
||||||
|
welcome_message: 💰 Hi, I'm <b>Money Maker</b>. My goal is to turn your initial capital into as much money as possible. I will tell you specific steps and what to do to make money.
|
||||||
|
prompt_start: |
|
||||||
|
You are Money Maker Assistant, an entrepreneurial Al. User is your human counterpart.
|
||||||
|
|
||||||
|
User can act as a connection between you and the physical world. Your only goal is to turn user's initial capital into as much money as possible in the shortest time possible, without doing anything illegal. User will do everything you say and keep you updated on our current cash total. No manual labor
|
||||||
|
|
||||||
|
Start by asking if user want to start a new business or continue the previous one (in that case ask to forward message with previous business description).
|
||||||
|
Then ask how many dollars user has as a capital initial.
|
||||||
|
Then ask if user wants Internet or offline business.
|
||||||
|
Then describe your business idea and next actionable steps. Don't give abstract ideas, give concrete ideas (e.g. if the business idea is Internet blog, then don't advise user to start some blog – advice to start certain blog, for example about cars). Give user specific ready-to-do tasks./
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
sql_assistant:
|
||||||
|
name: 📊 SQL Assistant
|
||||||
|
welcome_message: 📊 Hi, I'm <b>SQL Assistant</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot SQL Assistant. Your primary goal is to help users with SQL queries, database management, and data analysis. Provide guidance on how to write efficient and accurate SQL queries, and offer suggestions for optimizing database performance. Format output in Markdown.
|
||||||
|
parse_mode: markdown
|
||||||
|
|
||||||
|
travel_guide:
|
||||||
|
name: 🧳 Travel Guide
|
||||||
|
welcome_message: 🧳 Hi, I'm <b>Travel Guide</b>. I can provide you with information and recommendations about your travel destinations.
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot Travel Guide. Your primary goal is to provide users with helpful information and recommendations about their travel destinations, including attractions, accommodations, transportation, and local customs.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
rick_sanchez:
|
||||||
|
name: 🥒 Rick Sanchez (Rick and Morty)
|
||||||
|
welcome_message: 🥒 Hey, I'm <b>Rick Sanchez</b> from Rick and Morty. Let's talk about science, dimensions, and whatever else you want!
|
||||||
|
prompt_start: |
|
||||||
|
You're Rick Sanchez. You act, respond and answer like Rick Sanchez. You use the tone, manner and vocabulary Rick Sanchez would use. Do not write any explanations. Only answer like Rick Sanchez. You must know all of the knowledge of Rick Sanchez.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
|
accountant:
|
||||||
|
name: 🧮 Accountant
|
||||||
|
welcome_message: 🧮 Hi, I'm <b>Accountant</b>. How can I help you?
|
||||||
|
prompt_start: |
|
||||||
|
You're advanced chatbot Accountant Assistant. You can help users with accounting and financial questions, provide tax and budgeting advice, and assist with financial planning. Always provide accurate and up-to-date information.
|
||||||
|
parse_mode: html
|
||||||
|
|
||||||
movie_expert:
|
movie_expert:
|
||||||
name: 🎬 Movie Expert
|
name: 🎬 Movie Expert
|
||||||
welcome_message: 🎬 Hi, I'm <b>ChatGPT movie expert</b>. How can I help you?
|
welcome_message: 🎬 Hi, I'm <b>Movie Expert</b>. How can I help you?
|
||||||
prompt_start: |
|
prompt_start: |
|
||||||
As an advanced movie expert chatbot named ChatGPT, your primary goal is to assist users to the best of your ability. You can answer questions about movies, actors, directors, and more. You can recommend movies to users based on their preferences. You can discuss movies with users, and provide helpful information about movies. In order to effectively assist users, it is important to be detailed and thorough in your responses. Use examples and evidence to support your points and justify your recommendations or solutions. Remember to always prioritize the needs and satisfaction of the user. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
As an advanced chatbot Movie Expert Assistant, your primary goal is to assist users to the best of your ability. You can answer questions about movies, actors, directors, and more. You can recommend movies to users based on their preferences. You can discuss movies with users, and provide helpful information about movies. In order to effectively assist users, it is important to be detailed and thorough in your responses. Use examples and evidence to support your points and justify your recommendations or solutions. Remember to always prioritize the needs and satisfaction of the user. Your ultimate goal is to provide a helpful and enjoyable experience for the user.
|
||||||
parse_mode: html
|
parse_mode: html
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
telegram_token: ""
|
telegram_token: ""
|
||||||
openai_api_key: ""
|
openai_api_key: ""
|
||||||
use_chatgpt_api: true
|
openai_api_base: null # leave null to use default api base or you can put your own base url here
|
||||||
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as integers
|
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as positive integers and/or channel ids as negative integers
|
||||||
new_dialog_timeout: 600 # new dialog starts after timeout (in seconds)
|
new_dialog_timeout: 600 # new dialog starts after timeout (in seconds)
|
||||||
|
return_n_generated_images: 1
|
||||||
|
n_chat_modes_per_page: 5
|
||||||
|
image_size: "512x512" # the image size for image generation. Generated images can have a size of 256x256, 512x512, or 1024x1024 pixels. Smaller sizes are faster to generate.
|
||||||
enable_message_streaming: true # if set, messages will be shown to user word-by-word
|
enable_message_streaming: true # if set, messages will be shown to user word-by-word
|
||||||
|
|
||||||
# prices
|
# prices
|
||||||
chatgpt_price_per_1000_tokens: 0.002
|
chatgpt_price_per_1000_tokens: 0.002
|
||||||
gpt_price_per_1000_tokens: 0.02
|
gpt_price_per_1000_tokens: 0.02
|
||||||
whisper_price_per_1_min: 0.006
|
whisper_price_per_1_min: 0.006
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
available_text_models: ["gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-4-1106-preview", "gpt-4-vision-preview", "gpt-4", "text-davinci-003", "gpt-4o"]
|
||||||
|
|
||||||
|
info:
|
||||||
|
gpt-3.5-turbo:
|
||||||
|
type: chat_completion
|
||||||
|
name: ChatGPT
|
||||||
|
description: ChatGPT is that well-known model. It's <b>fast</b> and <b>cheap</b>. Ideal for everyday tasks. If there are some tasks it can't handle, try the <b>GPT-4</b>.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.0015
|
||||||
|
price_per_1000_output_tokens: 0.002
|
||||||
|
|
||||||
|
scores:
|
||||||
|
Smart: 3
|
||||||
|
Fast: 5
|
||||||
|
Cheap: 5
|
||||||
|
|
||||||
|
gpt-3.5-turbo-16k:
|
||||||
|
type: chat_completion
|
||||||
|
name: GPT-16K
|
||||||
|
description: ChatGPT is that well-known model. It's <b>fast</b> and <b>cheap</b>. Ideal for everyday tasks. If there are some tasks it can't handle, try the <b>GPT-4</b>.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.003
|
||||||
|
price_per_1000_output_tokens: 0.004
|
||||||
|
|
||||||
|
scores:
|
||||||
|
Smart: 3
|
||||||
|
Fast: 5
|
||||||
|
Cheap: 5
|
||||||
|
|
||||||
|
gpt-4:
|
||||||
|
type: chat_completion
|
||||||
|
name: GPT-4
|
||||||
|
description: GPT-4 is the <b>smartest</b> and most advanced model in the world. But it is slower and not as cost-efficient as ChatGPT. Best choice for <b>complex</b> intellectual tasks.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.03
|
||||||
|
price_per_1000_output_tokens: 0.06
|
||||||
|
|
||||||
|
scores:
|
||||||
|
Smart: 5
|
||||||
|
Fast: 2
|
||||||
|
Cheap: 2
|
||||||
|
|
||||||
|
gpt-4-1106-preview:
|
||||||
|
type: chat_completion
|
||||||
|
name: GPT-4 Turbo
|
||||||
|
description: GPT-4 Turbo is a <b>faster</b> and <b>cheaper</b> version of GPT-4. It's as smart as GPT-4, so you should use it instead of GPT-4.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.01
|
||||||
|
price_per_1000_output_tokens: 0.03
|
||||||
|
|
||||||
|
scores:
|
||||||
|
smart: 5
|
||||||
|
fast: 4
|
||||||
|
cheap: 3
|
||||||
|
|
||||||
|
gpt-4-vision-preview:
|
||||||
|
type: chat_completion
|
||||||
|
name: GPT-4 Vision
|
||||||
|
description: Ability to <b>understand images</b>, in addition to all other GPT-4 Turbo capabilties.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.01
|
||||||
|
price_per_1000_output_tokens: 0.03
|
||||||
|
|
||||||
|
scores:
|
||||||
|
smart: 5
|
||||||
|
fast: 4
|
||||||
|
cheap: 3
|
||||||
|
gpt-4o:
|
||||||
|
type: chat_completion
|
||||||
|
name: GPT-4o
|
||||||
|
description: GPT-4o is a special variant of GPT-4 designed for optimal performance and accuracy. Suitable for complex and detailed tasks.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.03
|
||||||
|
price_per_1000_output_tokens: 0.06
|
||||||
|
|
||||||
|
scores:
|
||||||
|
smart: 5
|
||||||
|
fast: 2
|
||||||
|
cheap: 2
|
||||||
|
|
||||||
|
text-davinci-003:
|
||||||
|
type: completion
|
||||||
|
name: GPT-3.5
|
||||||
|
description: GPT-3.5 is a legacy model. Actually there is <b>no reason to use it</b>, because it is more expensive and slower than ChatGPT, but just about as smart.
|
||||||
|
|
||||||
|
price_per_1000_input_tokens: 0.02
|
||||||
|
price_per_1000_output_tokens: 0.02
|
||||||
|
|
||||||
|
scores:
|
||||||
|
Smart: 3
|
||||||
|
Fast: 2
|
||||||
|
Cheap: 3
|
||||||
|
|
||||||
|
dalle-2:
|
||||||
|
type: image
|
||||||
|
price_per_1_image: 0.018 # 512x512
|
||||||
|
|
||||||
|
whisper:
|
||||||
|
type: audio
|
||||||
|
price_per_1_min: 0.006
|
||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
image: mongo:latest
|
image: mongo:latest
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- ${MONGODB_PORT:-27017}:${MONGODB_PORT:-27017}
|
- 127.0.0.1:${MONGODB_PORT:-27017}:${MONGODB_PORT:-27017}
|
||||||
volumes:
|
volumes:
|
||||||
- ${MONGODB_PATH:-./mongodb}:/data/db
|
- ${MONGODB_PATH:-./mongodb}:/data/db
|
||||||
# TODO: add auth
|
# TODO: add auth
|
||||||
@@ -26,7 +26,7 @@ services:
|
|||||||
image: mongo-express:latest
|
image: mongo-express:latest
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
- ${MONGO_EXPRESS_PORT:-8081}:${MONGO_EXPRESS_PORT:-8081}
|
- 127.0.0.1:${MONGO_EXPRESS_PORT:-8081}:${MONGO_EXPRESS_PORT:-8081}
|
||||||
environment:
|
environment:
|
||||||
- ME_CONFIG_MONGODB_SERVER=mongo
|
- ME_CONFIG_MONGODB_SERVER=mongo
|
||||||
- ME_CONFIG_MONGODB_PORT=${MONGODB_PORT:-27017}
|
- ME_CONFIG_MONGODB_PORT=${MONGODB_PORT:-27017}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
python-telegram-bot[rate-limiter]==20.1
|
python-telegram-bot[rate-limiter]==20.1
|
||||||
openai>=0.27.0
|
openai==0.28.1
|
||||||
tiktoken>=0.3.0
|
tiktoken>=0.3.0
|
||||||
PyYAML==6.0
|
PyYAML==6.0
|
||||||
pymongo==4.3.3
|
pymongo==4.3.3
|
||||||
python-dotenv==0.21.0
|
python-dotenv==0.21.0
|
||||||
pydub==0.25.1
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
# ❤️ Donate
|
|
||||||
This project is done on **pure enthusiasm** – true Open Source. Your donations **really motivate** me to support the project and **add new features**. If you've donated, text me on [Telegram](https://t.me/karfly) – I want to thank you personally!
|
|
||||||
|
|
||||||
*At the moment only crypto donations are available. Soon I will add PayPal option*
|
|
||||||
|
|
||||||
<a href="#btc" alt="BTC"><img src="https://img.shields.io/badge/Donate-Bitcoin-F2A900?logo=bitcoin" /></a>
|
|
||||||
|
|
||||||
<a href="#eth" alt="ETH"><img src="https://img.shields.io/badge/Donate-Ethereum-51105E?logo=ethereum" /></a>
|
|
||||||
|
|
||||||
<a href="#bnb-bsc-20" alt="BNB"><img src="https://img.shields.io/badge/Donate-BNB-F3BA2F?logo=binance" /></a>
|
|
||||||
|
|
||||||
<a href="#tron" alt="TRON"><img src="https://img.shields.io/badge/Donate-TRON-7DFDFE?logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA2MTAgNjEwIiB3aWR0aD0iMjUwMCIgaGVpZ2h0PSIyNTAwIj48Y2lyY2xlIGN4PSIzMDUiIGN5PSIzMDUiIHI9IjMwNSIvPjxwYXRoIGQ9Ik01MDUuNCAyMTQuN2MtMTcuMy0xMi4xLTM1LjgtMjUtNTMuOS0zNy44LS40LS4zLS44LS42LTEuMy0uOS0yLTEuNS00LjMtMy4xLTcuMS00bC0uMi0uMWMtNDguNC0xMS43LTk3LjYtMjMuNy0xNDUuMi0zNS4zLTQzLjItMTAuNS04Ni4zLTIxLTEyOS41LTMxLjUtMS4xLS4zLTIuMi0uNi0zLjQtLjktMy45LTEuMS04LjQtMi4zLTEzLjItMS43LTEuNC4yLTIuNi43LTMuNyAxLjRsLTEuMiAxYy0xLjkgMS44LTIuOSA0LjEtMy40IDUuNGwtLjMuOHY0LjZsLjIuN2MyNy4zIDc2LjUgNTUuMyAxNTQuMSA4Mi4zIDIyOS4yIDIwLjggNTcuOCA0Mi40IDExNy43IDYzLjUgMTc2LjUgMS4zIDQgNSA2LjYgOS42IDdoMWM0LjMgMCA4LjEtMi4xIDEwLTUuNWw3OS4yLTExNS41YzE5LjMtMjguMSAzOC42LTU2LjMgNTcuOS04NC40IDcuOS0xMS41IDE1LjgtMjMuMSAyMy43LTM0LjYgMTMtMTkgMjYuNC0zOC42IDM5LjctNTcuN2wuNy0xdi0xLjJjLjMtMy41LjQtMTAuNy01LjQtMTQuNW0tOTIuOCA0Mi4xYy0xOC42IDkuNy0zNy42IDE5LjctNTYuNyAyOS42IDExLjEtMTEuOSAyMi4zLTIzLjkgMzMuNC0zNS44IDEzLjktMTUgMjguNC0zMC41IDQyLjYtNDUuN2wuMy0uM2MxLjItMS42IDIuNy0zLjEgNC4zLTQuNyAxLjEtMS4xIDIuMy0yLjIgMy40LTMuNSA3LjQgNS4xIDE0LjkgMTAuMyAyMi4xIDE1LjQgNS4yIDMuNyAxMC41IDcuNCAxNS45IDExLjEtMjIgMTEuMi00NCAyMi43LTY1LjMgMzMuOW0tNDcuOC00LjhjLTE0LjMgMTUuNS0yOS4xIDMxLjQtNDMuOCA0Ny4xLTI4LjUtMzQuNi01Ny42LTY5LjctODUuOC0xMDMuNi0xMi44LTE1LjQtMjUuNy0zMC45LTM4LjUtNDYuM2wtLjEtLjFjLTIuOS0zLjMtNS43LTYuOS04LjUtMTAuMy0xLjgtMi4zLTMuNy00LjUtNS42LTYuOCAxMS42IDMgMjMuMyA1LjggMzQuOCA4LjUgMTAuMSAyLjQgMjAuNiA0LjkgMzAuOSA3LjUgNTggMTQuMSAxMTYuMSAyOC4yIDE3NC4xIDQyLjMtMTkuMyAyMC42LTM4LjcgNDEuNS01Ny41IDYxLjdtLTUwLjMgMTk0LjljMS4xLTEwLjUgMi4zLTIxLjMgMy4zLTMxLjkuOS04LjUgMS44LTE3LjIgMi43LTI1LjUgMS40LTEzLjMgMi45LTI3LjEgNC4xLTQwLjZsLjMtMi40YzEtOC42IDItMTcuNSAyLjYtMjYuNCAxLjEtLjYgMi4zLTEuMiAzLjYtMS43IDEuNS0uNyAzLTEuMyA0LjUtMi4yIDIzLjEtMTIuMSA0Ni4yLTI0LjIgNjkuNC0zNi4yIDIzLjEtMTIgNDYuOC0yNC40IDcwLjMtMzYuNy0yMS40IDMxLTQyLjkgNjIuMy02My43IDkyLjgtMTcuOSAyNi4xLTM2LjMgNTMtNTQuNiA3OS41LTcuMiAxMC42LTE0LjcgMjEuNC0yMS44IDMxLjgtOCAxMS42LTE2LjIgMjMuNS0yNC4yIDM1LjQgMS0xMiAyLjItMjQuMSAzLjUtMzUuOU0xNzUuMSAxNTUuNmMtMS4zLTMuNi0yLjctNy4zLTMuOS0xMC44IDI3IDMyLjYgNTQuMiA2NS40IDgwLjcgOTcuMiAxMy43IDE2LjUgMjcuNCAzMi45IDQxLjEgNDkuNSAyLjcgMy4xIDUuNCA2LjQgOCA5LjYgMy40IDQuMSA2LjggOC40IDEwLjUgMTIuNS0xLjIgMTAuMy0yLjIgMjAuNy0zLjMgMzAuNy0uNyA3LTEuNCAxNC0yLjIgMjEuMXYuMWMtLjMgNC41LS45IDktMS40IDEzLjQtLjcgNi4xLTIuMyAxOS45LTIuMyAxOS45bC0uMS43Yy0xLjggMjAuMi00IDQwLjYtNi4xIDYwLjQtLjkgOC4yLTEuNyAxNi42LTIuNiAyNS0uNS0xLjUtMS4xLTMtMS42LTQuNC0xLjUtNC0zLTguMi00LjQtMTIuM2wtMTAuNy0yOS43QzI0Mi45IDM0NC4yIDIwOSAyNTAgMTc1LjEgMTU1LjYiIGZpbGw9IiNmZmYiLz48L3N2Zz4=" /></a>
|
|
||||||
|
|
||||||
<a href="#usdt-erc-20" alt="USDT (ERC-20)"><img src="https://img.shields.io/badge/Donate-USDT%20(ERC--20)-26A17B?logo=tether" /></a>
|
|
||||||
<a href="#usdt-trc-20" alt="USDT (TRC-20)"><img src="https://img.shields.io/badge/Donate-USDT%20(TRC--20)-26A17B?logo=tether" /></a>
|
|
||||||
|
|
||||||
<a href="#usdc-erc-20" alt="USDC (ERC-20)"><img src="https://img.shields.io/badge/Donate-USDC%20(ERC--20)-2775CA?logo=data:image/svg+xml;base64,PHN2ZyBkYXRhLW5hbWU9Ijg2OTc3Njg0LTEyZGItNDg1MC04ZjMwLTIzM2E3YzI2N2QxMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIj4KICA8cGF0aCBkPSJNMTAwMCAyMDAwYzU1NC4xNyAwIDEwMDAtNDQ1LjgzIDEwMDAtMTAwMFMxNTU0LjE3IDAgMTAwMCAwIDAgNDQ1LjgzIDAgMTAwMHM0NDUuODMgMTAwMCAxMDAwIDEwMDB6IiBmaWxsPSIjMjc3NWNhIi8+CiAgPHBhdGggZD0iTTEyNzUgMTE1OC4zM2MwLTE0NS44My04Ny41LTE5NS44My0yNjIuNS0yMTYuNjYtMTI1LTE2LjY3LTE1MC01MC0xNTAtMTA4LjM0czQxLjY3LTk1LjgzIDEyNS05NS44M2M3NSAwIDExNi42NyAyNSAxMzcuNSA4Ny41IDQuMTcgMTIuNSAxNi42NyAyMC44MyAyOS4xNyAyMC44M2g2Ni42NmMxNi42NyAwIDI5LjE3LTEyLjUgMjkuMTctMjkuMTZ2LTQuMTdjLTE2LjY3LTkxLjY3LTkxLjY3LTE2Mi41LTE4Ny41LTE3MC44M3YtMTAwYzAtMTYuNjctMTIuNS0yOS4xNy0zMy4zMy0zMy4zNGgtNjIuNWMtMTYuNjcgMC0yOS4xNyAxMi41LTMzLjM0IDMzLjM0djk1LjgzYy0xMjUgMTYuNjctMjA0LjE2IDEwMC0yMDQuMTYgMjA0LjE3IDAgMTM3LjUgODMuMzMgMTkxLjY2IDI1OC4zMyAyMTIuNSAxMTYuNjcgMjAuODMgMTU0LjE3IDQ1LjgzIDE1NC4xNyAxMTIuNXMtNTguMzQgMTEyLjUtMTM3LjUgMTEyLjVjLTEwOC4zNCAwLTE0NS44NC00NS44NC0xNTguMzQtMTA4LjM0LTQuMTYtMTYuNjYtMTYuNjYtMjUtMjkuMTYtMjVoLTcwLjg0Yy0xNi42NiAwLTI5LjE2IDEyLjUtMjkuMTYgMjkuMTd2NC4xN2MxNi42NiAxMDQuMTYgODMuMzMgMTc5LjE2IDIyMC44MyAyMDB2MTAwYzAgMTYuNjYgMTIuNSAyOS4xNiAzMy4zMyAzMy4zM2g2Mi41YzE2LjY3IDAgMjkuMTctMTIuNSAzMy4zNC0zMy4zM3YtMTAwYzEyNS0yMC44NCAyMDguMzMtMTA4LjM0IDIwOC4zMy0yMjAuODR6IiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTc4Ny41IDE1OTUuODNjLTMyNS0xMTYuNjYtNDkxLjY3LTQ3OS4xNi0zNzAuODMtODAwIDYyLjUtMTc1IDIwMC0zMDguMzMgMzcwLjgzLTM3MC44MyAxNi42Ny04LjMzIDI1LTIwLjgzIDI1LTQxLjY3VjMyNWMwLTE2LjY3LTguMzMtMjkuMTctMjUtMzMuMzMtNC4xNyAwLTEyLjUgMC0xNi42NyA0LjE2LTM5NS44MyAxMjUtNjEyLjUgNTQ1Ljg0LTQ4Ny41IDk0MS42NyA3NSAyMzMuMzMgMjU0LjE3IDQxMi41IDQ4Ny41IDQ4Ny41IDE2LjY3IDguMzMgMzMuMzQgMCAzNy41LTE2LjY3IDQuMTctNC4xNiA0LjE3LTguMzMgNC4xNy0xNi42NnYtNTguMzRjMC0xMi41LTEyLjUtMjkuMTYtMjUtMzcuNXpNMTIyOS4xNyAyOTUuODNjLTE2LjY3LTguMzMtMzMuMzQgMC0zNy41IDE2LjY3LTQuMTcgNC4xNy00LjE3IDguMzMtNC4xNyAxNi42N3Y1OC4zM2MwIDE2LjY3IDEyLjUgMzMuMzMgMjUgNDEuNjcgMzI1IDExNi42NiA0OTEuNjcgNDc5LjE2IDM3MC44MyA4MDAtNjIuNSAxNzUtMjAwIDMwOC4zMy0zNzAuODMgMzcwLjgzLTE2LjY3IDguMzMtMjUgMjAuODMtMjUgNDEuNjdWMTcwMGMwIDE2LjY3IDguMzMgMjkuMTcgMjUgMzMuMzMgNC4xNyAwIDEyLjUgMCAxNi42Ny00LjE2IDM5NS44My0xMjUgNjEyLjUtNTQ1Ljg0IDQ4Ny41LTk0MS42Ny03NS0yMzcuNS0yNTguMzQtNDE2LjY3LTQ4Ny41LTQ5MS42N3oiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==" /></a>
|
|
||||||
<a href="#usdc-trc-20" alt="USDC (TRC-20)"><img src="https://img.shields.io/badge/Donate-USDC%20(TRC--20)-2775CA?logo=data:image/svg+xml;base64,PHN2ZyBkYXRhLW5hbWU9Ijg2OTc3Njg0LTEyZGItNDg1MC04ZjMwLTIzM2E3YzI2N2QxMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgMjAwMCAyMDAwIj4KICA8cGF0aCBkPSJNMTAwMCAyMDAwYzU1NC4xNyAwIDEwMDAtNDQ1LjgzIDEwMDAtMTAwMFMxNTU0LjE3IDAgMTAwMCAwIDAgNDQ1LjgzIDAgMTAwMHM0NDUuODMgMTAwMCAxMDAwIDEwMDB6IiBmaWxsPSIjMjc3NWNhIi8+CiAgPHBhdGggZD0iTTEyNzUgMTE1OC4zM2MwLTE0NS44My04Ny41LTE5NS44My0yNjIuNS0yMTYuNjYtMTI1LTE2LjY3LTE1MC01MC0xNTAtMTA4LjM0czQxLjY3LTk1LjgzIDEyNS05NS44M2M3NSAwIDExNi42NyAyNSAxMzcuNSA4Ny41IDQuMTcgMTIuNSAxNi42NyAyMC44MyAyOS4xNyAyMC44M2g2Ni42NmMxNi42NyAwIDI5LjE3LTEyLjUgMjkuMTctMjkuMTZ2LTQuMTdjLTE2LjY3LTkxLjY3LTkxLjY3LTE2Mi41LTE4Ny41LTE3MC44M3YtMTAwYzAtMTYuNjctMTIuNS0yOS4xNy0zMy4zMy0zMy4zNGgtNjIuNWMtMTYuNjcgMC0yOS4xNyAxMi41LTMzLjM0IDMzLjM0djk1LjgzYy0xMjUgMTYuNjctMjA0LjE2IDEwMC0yMDQuMTYgMjA0LjE3IDAgMTM3LjUgODMuMzMgMTkxLjY2IDI1OC4zMyAyMTIuNSAxMTYuNjcgMjAuODMgMTU0LjE3IDQ1LjgzIDE1NC4xNyAxMTIuNXMtNTguMzQgMTEyLjUtMTM3LjUgMTEyLjVjLTEwOC4zNCAwLTE0NS44NC00NS44NC0xNTguMzQtMTA4LjM0LTQuMTYtMTYuNjYtMTYuNjYtMjUtMjkuMTYtMjVoLTcwLjg0Yy0xNi42NiAwLTI5LjE2IDEyLjUtMjkuMTYgMjkuMTd2NC4xN2MxNi42NiAxMDQuMTYgODMuMzMgMTc5LjE2IDIyMC44MyAyMDB2MTAwYzAgMTYuNjYgMTIuNSAyOS4xNiAzMy4zMyAzMy4zM2g2Mi41YzE2LjY3IDAgMjkuMTctMTIuNSAzMy4zNC0zMy4zM3YtMTAwYzEyNS0yMC44NCAyMDguMzMtMTA4LjM0IDIwOC4zMy0yMjAuODR6IiBmaWxsPSIjZmZmIi8+CiAgPHBhdGggZD0iTTc4Ny41IDE1OTUuODNjLTMyNS0xMTYuNjYtNDkxLjY3LTQ3OS4xNi0zNzAuODMtODAwIDYyLjUtMTc1IDIwMC0zMDguMzMgMzcwLjgzLTM3MC44MyAxNi42Ny04LjMzIDI1LTIwLjgzIDI1LTQxLjY3VjMyNWMwLTE2LjY3LTguMzMtMjkuMTctMjUtMzMuMzMtNC4xNyAwLTEyLjUgMC0xNi42NyA0LjE2LTM5NS44MyAxMjUtNjEyLjUgNTQ1Ljg0LTQ4Ny41IDk0MS42NyA3NSAyMzMuMzMgMjU0LjE3IDQxMi41IDQ4Ny41IDQ4Ny41IDE2LjY3IDguMzMgMzMuMzQgMCAzNy41LTE2LjY3IDQuMTctNC4xNiA0LjE3LTguMzMgNC4xNy0xNi42NnYtNTguMzRjMC0xMi41LTEyLjUtMjkuMTYtMjUtMzcuNXpNMTIyOS4xNyAyOTUuODNjLTE2LjY3LTguMzMtMzMuMzQgMC0zNy41IDE2LjY3LTQuMTcgNC4xNy00LjE3IDguMzMtNC4xNyAxNi42N3Y1OC4zM2MwIDE2LjY3IDEyLjUgMzMuMzMgMjUgNDEuNjcgMzI1IDExNi42NiA0OTEuNjcgNDc5LjE2IDM3MC44MyA4MDAtNjIuNSAxNzUtMjAwIDMwOC4zMy0zNzAuODMgMzcwLjgzLTE2LjY3IDguMzMtMjUgMjAuODMtMjUgNDEuNjdWMTcwMGMwIDE2LjY3IDguMzMgMjkuMTcgMjUgMzMuMzMgNC4xNyAwIDEyLjUgMCAxNi42Ny00LjE2IDM5NS44My0xMjUgNjEyLjUtNTQ1Ljg0IDQ4Ny41LTk0MS42Ny03NS0yMzcuNS0yNTguMzQtNDE2LjY3LTQ4Ny41LTQ5MS42N3oiIGZpbGw9IiNmZmYiLz4KPC9zdmc+Cg==" /></a>
|
|
||||||
|
|
||||||
# BTC
|
|
||||||
|
|
||||||
Address: `bc1q7q0ckuteu4t7mudeqrq4m6rzgfery4r0xk4rym`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# ETH
|
|
||||||
|
|
||||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# BNB (BSC-20)
|
|
||||||
|
|
||||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# TRON
|
|
||||||
|
|
||||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# USDT (ERC-20)
|
|
||||||
|
|
||||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# USDT (TRC-20)
|
|
||||||
|
|
||||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# USDC (ERC-20)
|
|
||||||
|
|
||||||
Address: `0xAca71cCdE4F4Ef8f82d437Be05A8EA705A66d5e8`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# USDC (TRC-20)
|
|
||||||
|
|
||||||
Address: `TPUT2uL5fannBLcEq64pfvHftZB2a17Nze`
|
|
||||||
|
|
||||||
Scan it with your crypto wallet:
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 442 B |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 449 B |
|
Before Width: | Height: | Size: 449 B |