WladlenVK's picture
Update bot.py
54c2079 unverified
Raw History Blame Contribute Delete
10.6 kB
import os
import logging
import threading
import signal
import time
from flask import Flask, request, jsonify, send_from_directory
from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters
from telegram.request import HTTPXRequest # Добавлено для управления таймаутами
from supabase.client import create_client
import google.generativeai as genai
from groq import Groq
# --- 0. КОНТРОЛЬ ВЕРСИИ ---
VERSION = "20260417.01"
# --- 1. НАСТРОЙКА ЛОГИРОВАНИЯ ---
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
# --- 2. ИНИЦИАЛИЗАЦИЯ КЛЮЧЕЙ ---
TELEGRAM_TOKEN = os.environ.get("TELEGRAM_BOT_TOKEN")
SUPABASE_URL = os.environ.get("SUPABASE_URL")
SUPABASE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY")
GEMINI_KEY = os.environ.get("GEMINI_API_KEY")
GROQ_KEY = os.environ.get("GROQ_API_KEY")
ADMIN_ID = os.environ.get("MY_TELEGRAM_ID")
WEB_PASSWORD = os.environ.get("WEB_PASSWORD", "admin")
# --- 3. ИНИЦИАЛИЗАЦИЯ СЕРВИСОВ ---
supabase = create_client(SUPABASE_URL, SUPABASE_KEY)
genai.configure(api_key=GEMINI_KEY)
groq_client = Groq(api_key=GROQ_KEY)
# --- 4. ВЕБ-СЕРВЕР (FLASK) ---
app = Flask(__name__)
api_usage_counter = 0
telegram_usage_counter = 0
@app.route('/static/<path:path>')
def send_static(path):
"""Раздача логотипа и других файлов."""
return send_from_directory('static', path)
@app.route('/.well-known/ai-plugin.json')
def ai_plugin():
"""Манифест для внешних ИИ (ChatGPT и др.)."""
return jsonify({
"schema_version": "v1",
"name_for_human": "Evolution Prometheus",
"name_for_model": "evolution_prometheus",
"description_for_human": f"База знаний по книге 'Концепция развития'. Версия {VERSION}",
"description_for_model": "Доступ к философским и научным концепциям книги 'Концепция развития'.",
"auth": {"type": "none"},
"api": {"type": "openapi", "url": "https://wladlenvk-evolution-prometheus.hf.space/openapi.json"},
"logo_url": "https://i.ibb.co/C5Z95Jqs/logo.png",
"contact_email": "wladlenvk@gmail.com"
})
@app.route('/')
def admin_panel():
"""Главная страница и панель управления."""
global api_usage_counter, telegram_usage_counter
pwd = request.args.get('pwd')
if pwd != WEB_PASSWORD:
return (
f"<h1>Система 'Прометей'</h1>"
f"<p>Вер. {VERSION}</p>"
f"<p>Telegram сообщений: {telegram_usage_counter}</p>"
f"<p>API запросов: {api_usage_counter}</p>"
f"<p><i>Для доступа к управлению введите пароль в URL.</i></p>"
)
return f"""
<h1>Панель управления Прометей</h1>
<p>Версия: <b>{VERSION}</b></p>
<hr>
<p>Статистика: ТГ: {telegram_usage_counter} | API: {api_usage_counter}</p>
<form action="/action" method="get">
<input type="hidden" name="pwd" value="{WEB_PASSWORD}">
<button name="do" value="restart" style="padding:10px; background:red; color:white; border:none; cursor:pointer;">ПЕРЕЗАГРУЗИТЬ БОТА</button>
<button name="do" value="clear_history" style="padding:10px; margin-left:10px; cursor:pointer;">ОЧИСТИТЬ ВСЮ ИСТОРИЮ ЧАТОВ</button>
</form>
"""
@app.route('/action')
def handle_action():
pwd = request.args.get('pwd')
do = request.args.get('do')
if pwd != WEB_PASSWORD: return "Доступ запрещен", 403
if do == "restart":
threading.Thread(target=lambda: (time.sleep(1), os.kill(os.getpid(), signal.SIGTERM))).start()
return "<h1>Перезагрузка...</h1>"
if do == "clear_history":
try:
supabase.table("chat_history").delete().neq("user_id", "0").execute()
return "<h1>История очищена!</h1><a href='/?pwd=" + WEB_PASSWORD + "'>Назад</a>"
except Exception as e:
return f"Ошибка БД: {str(e)}"
return "Неизвестное действие"
@app.route('/openapi.json')
def openapi_spec():
"""Техпаспорт API."""
return jsonify({
"openapi": "3.0.0",
"info": {"title": "Prometheus Knowledge Base", "version": VERSION},
"servers": [{"url": "https://wladlenvk-evolution-prometheus.hf.space"}],
"paths": {
"/api/knowledge": {
"post": {
"summary": "Поиск в книге",
"operationId": "searchKnowledge",
"requestBody": {
"content": {"application/json": {"schema": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}}
},
"responses": {"200": {"description": "OK"}}
}
}
}
})
@app.route('/api/knowledge', methods=['POST'])
def external_ai_request():
global api_usage_counter
try:
data = request.json
user_query = data.get("query", "")
if not user_query: return jsonify({"error": "Empty query"}), 400
query_vector = get_embedding(user_query)
relevant_docs = search_docs(query_vector)
api_usage_counter += 1
return jsonify({
"source": "Концепция развития. Нетривиальный взгляд на эволюцию.",
"content": [doc['content'] for doc in relevant_docs] if relevant_docs else [],
"version": VERSION
})
except Exception as e:
return jsonify({"error": str(e)}), 500
def run_flask():
port = int(os.environ.get("PORT", 7860))
app.run(host='0.0.0.0', port=port)
# --- 5. ФУНКЦИИ ПАМЯТИ И RAG (БЕЗ ИЗМЕНЕНИЙ) ---
def save_chat_message(user_id, role, content):
try:
supabase.table("chat_history").insert({"user_id": user_id, "role": role, "content": content}).execute()
except Exception as e:
logging.error(f"Error saving history: {e}")
def get_chat_history(user_id, limit=10):
try:
res = supabase.table("chat_history").select("role", "content").eq("user_id", user_id).order("created_at", desc=True).limit(limit).execute()
return res.data[::-1]
except Exception as e:
logging.error(f"Error getting history: {e}")
return []
def get_embedding(text):
try:
result = genai.embed_content(model="models/gemini-embedding-001", content=text, task_type="retrieval_query")
return result['embedding']
except Exception as e:
raise Exception(f"Gemini Error: {str(e)}")
def search_docs(query_embedding):
try:
rpc_params = {'query_embedding': query_embedding, 'match_threshold': 0.5, 'match_count': 5}
response = supabase.rpc('match_documents', rpc_params).execute()
return response.data
except Exception as e:
raise Exception(f"Supabase Error: {str(e)}")
def split_text(text, max_length=4000):
return [text[i:i+max_length] for i in range(0, len(text), max_length)]
# --- 7. ОБРАБОТЧИКИ ТЕЛЕГРАМ (БЕЗ ИЗМЕНЕНИЙ) ---
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(f"Бот-консультант системы 'Прометей' (v{VERSION}) готов.")
async def restart_bot(update: Update, context: ContextTypes.DEFAULT_TYPE):
if str(update.message.from_user.id) == ADMIN_ID:
await update.message.reply_text("♻️ Перезагрузка...")
os.kill(os.getpid(), signal.SIGTERM)
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
global telegram_usage_counter
telegram_usage_counter += 1
user_id = update.message.from_user.id
user_text = update.message.text
status_msg = await update.message.reply_text("🔍 Анализирую...")
try:
save_chat_message(user_id, "user", user_text)
query_vector = get_embedding(user_text)
relevant_docs = search_docs(query_vector)
context_text = "\n\n".join([doc['content'] for doc in relevant_docs]) if relevant_docs else "Контекст не найден."
history = get_chat_history(user_id)
messages = [{"role": "system", "content": f"Ты тех-ассистент. Отвечай строго по контексту.\n\nКОНТЕКСТ:\n{context_text}"}]
for msg in history:
messages.append({"role": msg["role"], "content": msg["content"]})
completion = groq_client.chat.completions.create(model="llama-3.3-70b-versatile", messages=messages, temperature=0.1)
final_answer = completion.choices[0].message.content
save_chat_message(user_id, "assistant", final_answer)
if len(final_answer) <= 4096:
await status_msg.edit_text(final_answer)
else:
await status_msg.delete()
for chunk in split_text(final_answer):
await update.message.reply_text(chunk)
except Exception as e:
logging.error(f"Error: {e}")
await update.message.reply_text(f"⚠️ Ошибка: {str(e)}")
if __name__ == '__main__':
if not TELEGRAM_TOKEN:
print("CRITICAL ERROR: No Token")
else:
# Увеличенные таймауты оставляем — это полезная практика для облачных хостингов
t_request = HTTPXRequest(
connect_timeout=45,
read_timeout=45
)
# Запуск Flask в отдельном потоке
threading.Thread(target=run_flask, daemon=True).start()
# Сборка и запуск бота
application = ApplicationBuilder().token(TELEGRAM_TOKEN).request(t_request).build()
application.add_handler(CommandHandler('start', start))
application.add_handler(CommandHandler('restart', restart_bot))
application.add_handler(MessageHandler(filters.TEXT & (~filters.COMMAND), handle_message))
print(f"--- СИСТЕМА ПРОМЕТЕЙ {VERSION} ЗАПУЩЕНА ---")
application.run_polling()