2026-05-22 19:34:48 +03:00
|
|
|
import random
|
|
|
|
|
import re
|
|
|
|
|
import shelve
|
|
|
|
|
import time
|
|
|
|
|
from configparser import DEFAULTSECT
|
|
|
|
|
|
2026-07-09 12:34:37 +03:00
|
|
|
from pprint import pprint
|
|
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
from openai import AsyncOpenAI
|
|
|
|
|
|
2026-05-23 18:09:50 +03:00
|
|
|
from statistics import *
|
2026-05-22 19:34:48 +03:00
|
|
|
from colors import *
|
|
|
|
|
from config import *
|
|
|
|
|
|
|
|
|
|
last_command_time = {}
|
2026-07-05 15:43:52 +03:00
|
|
|
last_error_time = {}
|
2026-05-22 19:34:48 +03:00
|
|
|
last_block_time = {}
|
|
|
|
|
user_contexts = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_clients():
|
|
|
|
|
provs = {}
|
|
|
|
|
for provider, settings in PROVIDERS.items():
|
|
|
|
|
if settings.get("module") == "openai":
|
|
|
|
|
provs[provider] = AsyncOpenAI(
|
|
|
|
|
base_url=settings.get("url"), api_key=settings.get("key")
|
|
|
|
|
)
|
|
|
|
|
return provs
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
providers = get_clients()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clean_tail_repeats(text: str, max_repeat: int = 10):
|
|
|
|
|
if not text:
|
|
|
|
|
return text, 0
|
|
|
|
|
|
|
|
|
|
original_length = len(text)
|
|
|
|
|
cleaned = text
|
|
|
|
|
|
|
|
|
|
pattern = rf"(.)\1{{{max_repeat},}}$"
|
|
|
|
|
|
|
|
|
|
def limit_repeats(match):
|
|
|
|
|
char = match.group(1)
|
|
|
|
|
return char * max_repeat
|
|
|
|
|
|
|
|
|
|
cleaned = re.sub(pattern, limit_repeats, cleaned)
|
|
|
|
|
|
|
|
|
|
pattern_space = rf"((\S)\s+)\2{{{max_repeat},}}$"
|
|
|
|
|
cleaned = re.sub(
|
|
|
|
|
pattern_space,
|
|
|
|
|
lambda m: (m.group(2) + " ") * min(max_repeat, len(m.group(1))),
|
|
|
|
|
cleaned,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
words = cleaned.split()
|
|
|
|
|
if len(words) >= 2:
|
|
|
|
|
for i in range(1, min(len(words), 10)):
|
|
|
|
|
if all(words[-j] == words[-j - 1] for j in range(i)):
|
|
|
|
|
unique_words = words[: -(i + 1)]
|
|
|
|
|
cleaned = " ".join(unique_words + [words[-1]])
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
removed_count = original_length - len(cleaned)
|
|
|
|
|
|
|
|
|
|
cleaned = re.sub(r" +", " ", cleaned)
|
|
|
|
|
cleaned = cleaned.strip()
|
|
|
|
|
|
|
|
|
|
return cleaned, removed_count
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def parse_response(text: str):
|
|
|
|
|
if "Connection error." in text:
|
|
|
|
|
return {
|
|
|
|
|
"type": "connection",
|
|
|
|
|
"message": "ошибка подключения к провайдеру. подожди или смени провайдера в настройках узбекгпт.",
|
|
|
|
|
}
|
|
|
|
|
elif "Model quota exceeded" in text:
|
|
|
|
|
return {
|
|
|
|
|
"type": "quota",
|
|
|
|
|
"message": "лимит токенов закончился. подожди или смени модель.",
|
|
|
|
|
}
|
|
|
|
|
elif "tier capacity exceeded." in text:
|
|
|
|
|
return {"type": "tier", "message": "попробуй ещё раз!"}
|
|
|
|
|
elif "Internal server" in text:
|
|
|
|
|
return {
|
|
|
|
|
"type": "int",
|
|
|
|
|
"message": "произошла ошибка на стороне провайдера. подожди или смени провайдера в настройках узбекгпт.",
|
|
|
|
|
}
|
|
|
|
|
elif "(incomplete chunked read)" in text:
|
|
|
|
|
return {
|
|
|
|
|
"type": "incomplete",
|
|
|
|
|
"message": "сервер петух и оборвал соединение. подожди или смени провайдера в настройках узбекгпт.",
|
|
|
|
|
}
|
2026-07-09 12:34:37 +03:00
|
|
|
elif "invalid token" in text:
|
|
|
|
|
return {
|
|
|
|
|
"type": "incomplete",
|
|
|
|
|
"message": "указан неверный токен. смени провайдера или модель.",
|
|
|
|
|
}
|
2026-05-22 19:34:48 +03:00
|
|
|
else:
|
|
|
|
|
return {
|
|
|
|
|
"type": "unknown",
|
|
|
|
|
"message": f"произошла какая-то ошибка при генерации... {text}",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def galockinator(text):
|
|
|
|
|
for _ in range(random.randint(1, 3)):
|
|
|
|
|
if random.random() < 0.3:
|
|
|
|
|
text += "☝️"
|
|
|
|
|
else:
|
|
|
|
|
text += "✅"
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def remove_think_tags(text):
|
|
|
|
|
result = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
|
|
|
|
|
result = re.sub(r"<thought>.*?</thought>", "", text, flags=re.DOTALL)
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
2026-07-05 17:06:11 +03:00
|
|
|
def get_all_users(only_notify=False):
|
2026-05-23 18:09:50 +03:00
|
|
|
with shelve.open("users") as db:
|
2026-07-05 17:06:11 +03:00
|
|
|
return {k: db[k] for k in db.keys() if not only_notify or db[k].get("notify") == "da"}
|
2026-05-23 18:09:50 +03:00
|
|
|
|
|
|
|
|
|
2026-07-05 17:06:11 +03:00
|
|
|
def get_all_user_ids(only_notify=False):
|
2026-05-22 19:34:48 +03:00
|
|
|
ids = []
|
|
|
|
|
with shelve.open("users") as db:
|
|
|
|
|
for user_id in db.keys():
|
2026-07-05 17:06:11 +03:00
|
|
|
if only_notify and db[user_id].get("notify") != "da":
|
|
|
|
|
continue
|
2026-05-22 19:34:48 +03:00
|
|
|
try:
|
|
|
|
|
ids.append(int(user_id))
|
|
|
|
|
except ValueError:
|
|
|
|
|
pass
|
|
|
|
|
return ids
|
|
|
|
|
|
|
|
|
|
|
2026-07-05 17:06:11 +03:00
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
def set_user_config(
|
2026-07-05 17:06:11 +03:00
|
|
|
user_id,
|
|
|
|
|
model_name,
|
|
|
|
|
stream_mode=DEFAULT_STREAM,
|
|
|
|
|
call_mode=DEFAULT_CALL,
|
|
|
|
|
prompt_name=DEFAULT_PROMPT,
|
|
|
|
|
notify_mode=DEFAULT_NOTIFY,
|
2026-07-14 08:23:17 +03:00
|
|
|
markdown="",
|
2026-05-22 19:34:48 +03:00
|
|
|
):
|
|
|
|
|
with shelve.open("users") as db:
|
|
|
|
|
db[str(user_id)] = {
|
|
|
|
|
"model": model_name,
|
|
|
|
|
"stream": stream_mode,
|
|
|
|
|
"call": call_mode,
|
2026-07-05 17:06:11 +03:00
|
|
|
"prompt": prompt_name,
|
|
|
|
|
"notify": notify_mode,
|
2026-07-14 08:23:17 +03:00
|
|
|
"markdown": markdown,
|
2026-05-22 19:34:48 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-05-23 17:26:56 +03:00
|
|
|
def ensure_user(
|
|
|
|
|
user_id,
|
|
|
|
|
model_name=DEFAULT_MODEL,
|
|
|
|
|
stream_mode=DEFAULT_STREAM,
|
2026-07-05 17:06:11 +03:00
|
|
|
call_mode=DEFAULT_CALL,
|
|
|
|
|
prompt_name=DEFAULT_PROMPT,
|
2026-07-14 08:23:17 +03:00
|
|
|
notify_mode=DEFAULT_NOTIFY,
|
|
|
|
|
markdown=""
|
2026-05-23 17:26:56 +03:00
|
|
|
):
|
|
|
|
|
with shelve.open("users") as db:
|
|
|
|
|
key = str(user_id)
|
|
|
|
|
|
|
|
|
|
if key not in db:
|
|
|
|
|
db[key] = {
|
|
|
|
|
"model": model_name,
|
|
|
|
|
"stream": stream_mode,
|
|
|
|
|
"call": call_mode,
|
2026-07-05 17:06:11 +03:00
|
|
|
"prompt": prompt_name,
|
|
|
|
|
"notify": notify_mode,
|
2026-07-14 08:23:17 +03:00
|
|
|
"markdown": markdown,
|
2026-05-23 17:26:56 +03:00
|
|
|
}
|
|
|
|
|
|
2026-07-14 08:23:17 +03:00
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
def get_user_config(user_id):
|
|
|
|
|
with shelve.open("users") as db:
|
2026-07-14 08:23:17 +03:00
|
|
|
config = db.get(str(user_id), {})
|
|
|
|
|
defaults = {
|
|
|
|
|
"model": DEFAULT_MODEL,
|
|
|
|
|
"stream": DEFAULT_STREAM,
|
|
|
|
|
"call": DEFAULT_CALL,
|
|
|
|
|
"prompt": DEFAULT_PROMPT,
|
|
|
|
|
"notify": DEFAULT_NOTIFY,
|
|
|
|
|
"markdown": "",
|
|
|
|
|
}
|
|
|
|
|
updated = False
|
|
|
|
|
for key, value in defaults.items():
|
|
|
|
|
if key not in config:
|
|
|
|
|
config[key] = value
|
|
|
|
|
updated = True
|
|
|
|
|
if updated:
|
|
|
|
|
db[str(user_id)] = config
|
|
|
|
|
return config
|
|
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
async def generate(
|
2026-07-09 12:34:37 +03:00
|
|
|
prompt: str, bot=None, user_id=None, chat_id=None, message_id=None, images=None
|
2026-05-22 19:34:48 +03:00
|
|
|
) -> str:
|
|
|
|
|
if user_id:
|
|
|
|
|
if user_id not in user_contexts:
|
|
|
|
|
user_contexts[user_id] = []
|
|
|
|
|
|
|
|
|
|
config = get_user_config(user_id)
|
2026-05-22 20:04:26 +03:00
|
|
|
if "*" in config.get("model", ""):
|
|
|
|
|
model = config.get("model").split("*", 2)
|
|
|
|
|
else:
|
|
|
|
|
print(f"{RED} -- err model - failed to get prov&model in \"{config.get('model','')}\"{RESET}")
|
|
|
|
|
return "произошла ошибка при парсинге вашей выбранной модели. либо хост бота гандон либо тебе надо сменить модель в настройках!!"
|
2026-07-09 12:34:37 +03:00
|
|
|
|
|
|
|
|
settings = MODELS.get(model[0]).get(model[1])
|
2026-07-05 17:06:11 +03:00
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
stream_cf = config["stream"]
|
2026-07-05 17:06:11 +03:00
|
|
|
system_prompt_name = config["prompt"]
|
|
|
|
|
if system_prompt_name in SYSTEM_PROMPTS:
|
2026-07-09 16:07:31 +03:00
|
|
|
system_prompt = SYSTEM_PROMPTS[system_prompt_name] + ADD_TO_PROMPT
|
2026-07-14 08:23:17 +03:00
|
|
|
if config["markdown"] == "new":
|
|
|
|
|
system_prompt += NEW_MD
|
|
|
|
|
else:
|
|
|
|
|
system_prompt += OLD_MD
|
2026-07-05 17:06:11 +03:00
|
|
|
else:
|
|
|
|
|
text = "❌произошла ошибка, возможные проблемы:\n" \
|
|
|
|
|
f"1. у вас выбран удалённый сист промпт (`{system_prompt_name}`)\n" \
|
|
|
|
|
f"2. конфиг бота настроен неправильно и надо связаться с тем кто его хостит"
|
|
|
|
|
return text
|
2026-05-22 19:34:48 +03:00
|
|
|
|
|
|
|
|
if stream_cf == "native" and user_id == chat_id:
|
|
|
|
|
stream = True
|
|
|
|
|
elif stream_cf == "edit" and chat_id and message_id:
|
|
|
|
|
stream = True
|
|
|
|
|
else:
|
|
|
|
|
stream = False
|
|
|
|
|
|
2026-07-09 12:34:37 +03:00
|
|
|
content = []
|
|
|
|
|
|
|
|
|
|
if images:
|
|
|
|
|
if settings.get("vision"):
|
|
|
|
|
for image in images:
|
|
|
|
|
content += [{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image}"}}]
|
|
|
|
|
else:
|
|
|
|
|
for image in images:
|
|
|
|
|
try:
|
|
|
|
|
image_text = await image2text(image)
|
|
|
|
|
prompt = f"<фото>{image_text}</фото>\n{prompt}"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"{RED} -- err photo - {e} : {config.get('model', '')}{RESET}")
|
|
|
|
|
|
|
|
|
|
content += [{"type": "text", "text": prompt}]
|
|
|
|
|
|
|
|
|
|
user_contexts[user_id].append({"role": "user", "content": content})
|
2026-05-22 19:34:48 +03:00
|
|
|
user_contexts[user_id] = user_contexts[user_id][-MAX_CONTEXT:]
|
|
|
|
|
|
2026-07-05 17:06:11 +03:00
|
|
|
messages = [{"role": "system", "content": system_prompt}] + user_contexts[
|
2026-05-22 19:34:48 +03:00
|
|
|
user_id
|
|
|
|
|
]
|
|
|
|
|
else:
|
|
|
|
|
model = DEFAULT_MODEL.split("*", 2)
|
2026-07-09 12:34:37 +03:00
|
|
|
settings = MODELS.get(model[0]).get(model[1])
|
2026-05-22 19:34:48 +03:00
|
|
|
stream = False
|
|
|
|
|
stream_cf = "none"
|
2026-07-09 12:34:37 +03:00
|
|
|
content = []
|
|
|
|
|
if images:
|
|
|
|
|
if settings.get("vision"):
|
|
|
|
|
for image in images:
|
|
|
|
|
content += [{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image}"}}]
|
|
|
|
|
else:
|
|
|
|
|
for image in images:
|
|
|
|
|
try:
|
|
|
|
|
image_text = await image2text(image)
|
|
|
|
|
prompt = f"<фото>{image_text}</фото>\n{prompt}"
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"{RED} -- err photo - {e} : {model}{RESET}")
|
|
|
|
|
|
|
|
|
|
content += [{"type": "text", "text": prompt}]
|
2026-07-14 08:23:17 +03:00
|
|
|
messages = [{"role": "system", "content": SYSTEM_PROMPTS[DEFAULT_PROMPT]+ADD_TO_PROMPT+OLD_MD}] + [
|
2026-07-09 12:34:37 +03:00
|
|
|
{"role": "user", "content": content}
|
2026-05-22 19:34:48 +03:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
text = ""
|
|
|
|
|
|
2026-05-23 18:09:50 +03:00
|
|
|
add_one_to("stats/gens")
|
2026-07-05 14:52:57 +03:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
client = providers[model[0]]
|
|
|
|
|
except Exception:
|
2026-07-05 17:06:11 +03:00
|
|
|
text = "❌произошла ошибка, возможные проблемы:\n" \
|
2026-07-05 14:52:57 +03:00
|
|
|
"1. у вас выбрана удалённая модель или провайдер\n" \
|
2026-07-05 17:06:11 +03:00
|
|
|
"2. конфиг бота настроен неправильно и надо связаться с тем кто его хостит"
|
2026-07-05 14:52:57 +03:00
|
|
|
return text
|
|
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
text = await openai_generate_text(
|
|
|
|
|
oai=client,
|
|
|
|
|
bot=bot,
|
|
|
|
|
model=model[1],
|
2026-07-09 12:34:37 +03:00
|
|
|
settings=settings,
|
2026-05-22 19:34:48 +03:00
|
|
|
messages=messages,
|
|
|
|
|
stream=stream,
|
|
|
|
|
stream_cf=stream_cf,
|
|
|
|
|
chat_id=chat_id,
|
|
|
|
|
draft_id=chat_id,
|
|
|
|
|
message_id=message_id,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if user_id:
|
2026-07-09 12:34:37 +03:00
|
|
|
user_contexts[user_id].append({"role": "assistant", "content": [{"type": "text", "text": text}]})
|
2026-05-22 19:34:48 +03:00
|
|
|
user_contexts[user_id] = user_contexts[user_id][-MAX_CONTEXT:]
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def image2text(image) -> str:
|
|
|
|
|
if IMAGE_MODEL == "":
|
|
|
|
|
return ""
|
|
|
|
|
model = IMAGE_MODEL.split("*", 2)
|
|
|
|
|
client = providers[model[0]]
|
|
|
|
|
text = ""
|
|
|
|
|
messages = [
|
|
|
|
|
{
|
|
|
|
|
"role": "user",
|
|
|
|
|
"content": [
|
|
|
|
|
{"type": "input_text", "text": IMAGE_PROMPT},
|
|
|
|
|
{"type": "input_image", "image_url": f"data:image/jpeg;base64,{image}"},
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
response = await client.responses.create(
|
|
|
|
|
model=model[1], input=messages, stream=False
|
|
|
|
|
)
|
|
|
|
|
text = response.output_text
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def openai_generate_text(
|
|
|
|
|
oai: AsyncOpenAI,
|
|
|
|
|
bot,
|
|
|
|
|
model,
|
|
|
|
|
settings,
|
|
|
|
|
messages,
|
|
|
|
|
stream,
|
|
|
|
|
stream_cf,
|
|
|
|
|
chat_id,
|
|
|
|
|
draft_id,
|
|
|
|
|
message_id,
|
|
|
|
|
) -> str:
|
|
|
|
|
try:
|
2026-07-09 12:34:37 +03:00
|
|
|
|
2026-05-22 19:34:48 +03:00
|
|
|
completion = await oai.chat.completions.create(
|
|
|
|
|
model=model,
|
|
|
|
|
messages=messages,
|
|
|
|
|
stream=stream,
|
|
|
|
|
max_tokens=MAX_TOKENS,
|
|
|
|
|
extra_body={
|
2026-07-14 08:23:17 +03:00
|
|
|
"think": "false"
|
2026-05-22 19:34:48 +03:00
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
if stream:
|
|
|
|
|
message = ""
|
|
|
|
|
counter = 0
|
|
|
|
|
if not draft_id:
|
|
|
|
|
draft_id = chat_id
|
|
|
|
|
async for event in completion:
|
|
|
|
|
content = event.choices[0].delta.content
|
|
|
|
|
if content:
|
|
|
|
|
message += content
|
|
|
|
|
counter += 1
|
|
|
|
|
if stream_cf == "native":
|
|
|
|
|
counter_limit = settings.get("n_tok")
|
|
|
|
|
else:
|
|
|
|
|
counter_limit = settings.get("e_tok")
|
|
|
|
|
|
|
|
|
|
if counter % counter_limit == 0:
|
|
|
|
|
display_text, s = clean_tail_repeats(message)
|
|
|
|
|
if s > 0:
|
|
|
|
|
display_text += f"*... <{s} обрезано>*"
|
|
|
|
|
if display_text.count("```") % 2 != 0:
|
|
|
|
|
display_text += "\n```"
|
|
|
|
|
try:
|
|
|
|
|
if stream_cf == "native":
|
|
|
|
|
await bot.send_message_draft(
|
|
|
|
|
chat_id=chat_id,
|
|
|
|
|
draft_id=draft_id,
|
|
|
|
|
text=display_text,
|
|
|
|
|
parse_mode="Markdown",
|
|
|
|
|
)
|
|
|
|
|
elif stream_cf == "edit":
|
|
|
|
|
await bot.edit_message_text(
|
|
|
|
|
chat_id=chat_id,
|
|
|
|
|
message_id=message_id,
|
|
|
|
|
text=display_text,
|
|
|
|
|
parse_mode="Markdown",
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"{YELLOW} -- {e}{RESET}")
|
|
|
|
|
|
|
|
|
|
text = message
|
|
|
|
|
else:
|
|
|
|
|
text = completion.choices[0].message.content
|
|
|
|
|
except Exception as e:
|
|
|
|
|
error = parse_response(str(e))
|
|
|
|
|
print(f"{RED} -- {error['type']} - {e}{RESET}")
|
|
|
|
|
return error["message"]
|
|
|
|
|
else:
|
|
|
|
|
text, s = clean_tail_repeats(text)
|
|
|
|
|
if s > 0:
|
|
|
|
|
text += f"*... <{s} обрезано>*"
|
2026-07-09 12:34:37 +03:00
|
|
|
if text == "":
|
|
|
|
|
text = "(пустой ответ)"
|
2026-05-22 19:34:48 +03:00
|
|
|
return text
|