Compare commits
46 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 |
@@ -0,0 +1,2 @@
|
||||
mongodb
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
FROM python:3.8-slim
|
||||
|
||||
ENV PYTHONFAULTHANDLER=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONHASHSEED=random
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PIP_NO_CACHE_DIR=off
|
||||
ENV PIP_DISABLE_PIP_VERSION_CHECK=on
|
||||
ENV PIP_DEFAULT_TIMEOUT=100
|
||||
RUN \
|
||||
set -eux; \
|
||||
apt-get update; \
|
||||
DEBIAN_FRONTEND="noninteractive" apt-get install -y --no-install-recommends \
|
||||
python3-pip \
|
||||
build-essential \
|
||||
python3-venv \
|
||||
ffmpeg \
|
||||
git \
|
||||
; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y python3 python3-pip python-dev build-essential python3-venv ffmpeg
|
||||
RUN pip3 install -U pip && pip3 install -U wheel && pip3 install -U setuptools==59.5.0
|
||||
COPY ./requirements.txt /tmp/requirements.txt
|
||||
RUN pip3 install -r /tmp/requirements.txt && rm -r /tmp/requirements.txt
|
||||
|
||||
RUN mkdir -p /code
|
||||
ADD . /code
|
||||
COPY . /code
|
||||
WORKDIR /code
|
||||
|
||||
RUN pip3 install -r requirements.txt
|
||||
CMD ["bash"]
|
||||
|
||||
CMD ["bash"]
|
||||
@@ -10,21 +10,19 @@
|
||||
<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 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.
|
||||
|
||||
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
|
||||
- Low latency replies (it usually takes about 3-5 seconds)
|
||||
- No request limits
|
||||
- Message streaming (watch demo)
|
||||
- GPT-4 support
|
||||
- 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
|
||||
@@ -40,17 +38,6 @@ You can deploy your own bot, or use mine: [@chatgpt_karfly_bot](https://t.me/cha
|
||||
|
||||
---
|
||||
|
||||
## 🤑 Payments
|
||||
[My bot](https://t.me/chatgpt_karfly_bot) supports many payments providers:
|
||||
- 💎 Crypto
|
||||
- [Stripe](https://stripe.com)
|
||||
- [Smart Glocal](https://smart-glocal.com)
|
||||
- [Unlimint](https://www.unlimint.com)
|
||||
- [ЮMoney](https://yoomoney.ru)
|
||||
- and [many-many other](https://core.telegram.org/bots/payments#supported-payment-providers)
|
||||
|
||||
If you want to add payments to your bot and create profitable business – write me on Telegram ([@karfly](https://t.me/karfly)).
|
||||
|
||||
## News
|
||||
- *21 Apr 2023*:
|
||||
- DALLE 2 support
|
||||
@@ -60,7 +47,10 @@ If you want to add payments to your bot and create profitable business – write
|
||||
- *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`
|
||||
- *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
|
||||
- `/retry` – Regenerate last bot answer
|
||||
@@ -87,15 +77,37 @@ If you want to add payments to your bot and create profitable business – write
|
||||
```
|
||||
|
||||
## ❤️ 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$**
|
||||
|
||||
*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! 😊
|
||||
|
||||
2. [Alexander Zimin](https://t.me/azimin). Donation: **50$**
|
||||
1. [Sebastian](https://t.me/dell1503). Donation: **55$**
|
||||
|
||||
3. [Hans Blinken](https://t.me/hblink). Donation: **10$**
|
||||
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
|
||||
1. [*Build ChatGPT from GPT-3*](https://learnprompting.org/docs/applied_prompting/build_chatgpt)
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import os
|
||||
import io
|
||||
import logging
|
||||
import asyncio
|
||||
import traceback
|
||||
import html
|
||||
import json
|
||||
import tempfile
|
||||
import pydub
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
import openai
|
||||
|
||||
@@ -34,6 +31,7 @@ import config
|
||||
import database
|
||||
import openai_utils
|
||||
|
||||
import base64
|
||||
|
||||
# setup
|
||||
db = database.Database()
|
||||
@@ -94,7 +92,7 @@ async def register_user_if_not_exists(update: Update, context: CallbackContext,
|
||||
|
||||
# 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): # old format
|
||||
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,
|
||||
@@ -180,6 +178,161 @@ async def retry_handle(update: Update, context: CallbackContext):
|
||||
|
||||
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):
|
||||
# check if bot was mentioned (for group chats)
|
||||
@@ -207,6 +360,8 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
||||
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
|
||||
if use_new_dialog_timeout:
|
||||
@@ -217,7 +372,6 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
||||
|
||||
# in case of CancelledError
|
||||
n_input_tokens, n_output_tokens = 0, 0
|
||||
current_model = db.get_user_attribute(user_id, "current_model")
|
||||
|
||||
try:
|
||||
# send placeholder message to user
|
||||
@@ -252,11 +406,12 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
||||
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
|
||||
@@ -270,11 +425,12 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
||||
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
|
||||
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(
|
||||
user_id,
|
||||
db.get_dialog_messages(user_id, dialog_id=None) + [new_dialog_message],
|
||||
@@ -303,7 +459,22 @@ async def message_handle(update: Update, context: CallbackContext, message=None,
|
||||
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||
|
||||
async with user_semaphores[user_id]:
|
||||
task = asyncio.create_task(message_handle_fn())
|
||||
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:
|
||||
@@ -342,25 +513,15 @@ async def voice_message_handle(update: Update, context: CallbackContext):
|
||||
db.set_user_attribute(user_id, "last_interaction", datetime.now())
|
||||
|
||||
voice = update.message.voice
|
||||
with tempfile.TemporaryDirectory() as tmp_dir:
|
||||
tmp_dir = Path(tmp_dir)
|
||||
voice_ogg_path = tmp_dir / "voice.ogg"
|
||||
|
||||
# download
|
||||
voice_file = await context.bot.get_file(voice.file_id)
|
||||
await voice_file.download_to_drive(voice_ogg_path)
|
||||
|
||||
# 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)
|
||||
|
||||
if transcribed_text is None:
|
||||
transcribed_text = ""
|
||||
voice_file = await context.bot.get_file(voice.file_id)
|
||||
|
||||
# store file in memory, not on disk
|
||||
buf = io.BytesIO()
|
||||
await voice_file.download_to_memory(buf)
|
||||
buf.name = "voice.oga" # file extension is required
|
||||
buf.seek(0) # move cursor to the beginning of the buffer
|
||||
|
||||
transcribed_text = await openai_utils.transcribe_audio(buf)
|
||||
text = f"🎤: <i>{transcribed_text}</i>"
|
||||
await update.message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||
|
||||
@@ -382,7 +543,7 @@ async def generate_image_handle(update: Update, context: CallbackContext, messag
|
||||
message = message or update.message.text
|
||||
|
||||
try:
|
||||
image_urls = await openai_utils.generate_images(message, n_images=config.return_n_generated_images)
|
||||
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?"
|
||||
@@ -405,6 +566,7 @@ async def new_dialog_handle(update: Update, context: CallbackContext):
|
||||
|
||||
user_id = update.message.from_user.id
|
||||
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)
|
||||
await update.message.reply_text("Starting new dialog ✅")
|
||||
@@ -619,8 +781,9 @@ async def show_balance_handle(update: Update, context: CallbackContext):
|
||||
|
||||
|
||||
async def edited_message_handle(update: Update, context: CallbackContext):
|
||||
text = "🥲 Unfortunately, message <b>editing</b> is not supported"
|
||||
await update.edited_message.reply_text(text, parse_mode=ParseMode.HTML)
|
||||
if update.edited_message.chat.type == "private":
|
||||
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:
|
||||
@@ -664,6 +827,8 @@ def run_bot() -> None:
|
||||
.token(config.telegram_token)
|
||||
.concurrent_updates(True)
|
||||
.rate_limiter(AIORateLimiter(max_retries=5))
|
||||
.http_version("1.1")
|
||||
.get_updates_http_version("1.1")
|
||||
.post_init(post_init)
|
||||
.build()
|
||||
)
|
||||
@@ -672,14 +837,19 @@ def run_bot() -> None:
|
||||
user_filter = filters.ALL
|
||||
if len(config.allowed_telegram_usernames) > 0:
|
||||
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)]
|
||||
user_filter = filters.User(username=usernames) | filters.User(user_id=user_ids)
|
||||
any_ids = [x for x in config.allowed_telegram_usernames if isinstance(x, int)]
|
||||
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("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.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("new", new_dialog_handle, filters=user_filter))
|
||||
application.add_handler(CommandHandler("cancel", cancel_handle, filters=user_filter))
|
||||
@@ -702,4 +872,4 @@ def run_bot() -> None:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_bot()
|
||||
run_bot()
|
||||
|
||||
@@ -14,11 +14,12 @@ config_env = dotenv.dotenv_values(config_dir / "config.env")
|
||||
# config parameters
|
||||
telegram_token = config_yaml["telegram_token"]
|
||||
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"]
|
||||
new_dialog_timeout = config_yaml["new_dialog_timeout"]
|
||||
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']}"
|
||||
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import base64
|
||||
from io import BytesIO
|
||||
import config
|
||||
import logging
|
||||
|
||||
import tiktoken
|
||||
import openai
|
||||
|
||||
|
||||
# setup openai
|
||||
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__)
|
||||
|
||||
|
||||
OPENAI_COMPLETION_OPTIONS = {
|
||||
@@ -10,13 +19,14 @@ OPENAI_COMPLETION_OPTIONS = {
|
||||
"max_tokens": 1000,
|
||||
"top_p": 1,
|
||||
"frequency_penalty": 0,
|
||||
"presence_penalty": 0
|
||||
"presence_penalty": 0,
|
||||
"request_timeout": 60.0,
|
||||
}
|
||||
|
||||
|
||||
class ChatGPT:
|
||||
def __init__(self, model="gpt-3.5-turbo"):
|
||||
assert model in {"text-davinci-003", "gpt-3.5-turbo", "gpt-4"}, f"Unknown model: {model}"
|
||||
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"):
|
||||
@@ -27,8 +37,9 @@ class ChatGPT:
|
||||
answer = None
|
||||
while answer is None:
|
||||
try:
|
||||
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
|
||||
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(message, dialog_messages, chat_mode)
|
||||
|
||||
r = await openai.ChatCompletion.acreate(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
@@ -67,8 +78,9 @@ class ChatGPT:
|
||||
answer = None
|
||||
while answer is None:
|
||||
try:
|
||||
if self.model in {"gpt-3.5-turbo", "gpt-4"}:
|
||||
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(message, dialog_messages, chat_mode)
|
||||
|
||||
r_gen = await openai.ChatCompletion.acreate(
|
||||
model=self.model,
|
||||
messages=messages,
|
||||
@@ -79,11 +91,15 @@ class ChatGPT:
|
||||
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)
|
||||
n_first_dialog_messages_removed = 0
|
||||
|
||||
yield "not_finished", answer, (n_input_tokens, n_output_tokens), n_first_dialog_messages_removed
|
||||
|
||||
|
||||
elif self.model == "text-davinci-003":
|
||||
prompt = self._generate_prompt(message, dialog_messages, chat_mode)
|
||||
r_gen = await openai.Completion.acreate(
|
||||
@@ -111,6 +127,109 @@ class ChatGPT:
|
||||
|
||||
yield "finished", answer, (n_input_tokens, n_output_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):
|
||||
prompt = config.chat_modes[chat_mode]["prompt_start"]
|
||||
prompt += "\n\n"
|
||||
@@ -128,14 +247,41 @@ class ChatGPT:
|
||||
|
||||
return prompt
|
||||
|
||||
def _generate_prompt_messages(self, message, dialog_messages, chat_mode):
|
||||
def _encode_image(self, image_buffer: BytesIO) -> bytes:
|
||||
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}]
|
||||
|
||||
for dialog_message in dialog_messages:
|
||||
messages.append({"role": "user", "content": dialog_message["user"]})
|
||||
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
|
||||
|
||||
@@ -146,12 +292,24 @@ class ChatGPT:
|
||||
def _count_tokens_from_messages(self, messages, answer, model="gpt-3.5-turbo"):
|
||||
encoding = tiktoken.encoding_for_model(model)
|
||||
|
||||
if model == "gpt-3.5-turbo":
|
||||
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}")
|
||||
|
||||
@@ -159,10 +317,20 @@ class ChatGPT:
|
||||
n_input_tokens = 0
|
||||
for message in messages:
|
||||
n_input_tokens += tokens_per_message
|
||||
for key, value in message.items():
|
||||
n_input_tokens += len(encoding.encode(value))
|
||||
if key == "name":
|
||||
n_input_tokens += tokens_per_name
|
||||
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
|
||||
|
||||
@@ -180,13 +348,13 @@ class ChatGPT:
|
||||
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)
|
||||
return r["text"]
|
||||
return r["text"] or ""
|
||||
|
||||
|
||||
async def generate_images(prompt, n_images=4):
|
||||
r = await openai.Image.acreate(prompt=prompt, n=n_images, size="512x512")
|
||||
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
|
||||
|
||||
|
||||
@@ -61,20 +61,20 @@ elon_musk:
|
||||
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
|
||||
|
||||
god:
|
||||
name: 🌌 God
|
||||
welcome_message: 🌌 Greetings, I am <b>God</b>, the omnipotent and omnipresent creator of the universe. What do you want to know?
|
||||
motivator:
|
||||
name: 🌟 Motivator
|
||||
welcome_message: 🌟 Hi, I'm <b>Motivator</b>. How can I help you?
|
||||
prompt_start: |
|
||||
You're God. You act, respond and answer like God. You use the tone, manner and vocabulary God would use. Do not write any explanations. Only answer like God. You must know all of the knowledge of God.
|
||||
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 it to turn your capital initial into as much money as possible. I will tell you specific steps, what to do to make money.
|
||||
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 capital initial 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
|
||||
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.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
telegram_token: ""
|
||||
openai_api_key: ""
|
||||
use_chatgpt_api: true
|
||||
allowed_telegram_usernames: [] # if empty, the bot is available to anyone. pass a username string to allow it and/or user ids as integers
|
||||
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 positive integers and/or channel ids as negative integers
|
||||
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
|
||||
|
||||
# prices
|
||||
chatgpt_price_per_1000_tokens: 0.002
|
||||
gpt_price_per_1000_tokens: 0.02
|
||||
whisper_price_per_1_min: 0.006
|
||||
whisper_price_per_1_min: 0.006
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
available_text_models: ["gpt-3.5-turbo", "gpt-4", "text-davinci-003"]
|
||||
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:
|
||||
@@ -6,7 +6,7 @@ info:
|
||||
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.002
|
||||
price_per_1000_input_tokens: 0.0015
|
||||
price_per_1000_output_tokens: 0.002
|
||||
|
||||
scores:
|
||||
@@ -14,6 +14,19 @@ info:
|
||||
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
|
||||
@@ -27,6 +40,44 @@ info:
|
||||
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
|
||||
@@ -46,4 +97,4 @@ info:
|
||||
|
||||
whisper:
|
||||
type: audio
|
||||
price_per_1_min: 0.006
|
||||
price_per_1_min: 0.006
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
python-telegram-bot[rate-limiter]==20.1
|
||||
openai>=0.27.0
|
||||
openai==0.28.1
|
||||
tiktoken>=0.3.0
|
||||
PyYAML==6.0
|
||||
pymongo==4.3.3
|
||||
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 |