Really-amin commited on
Commit
189c917
1 Parent(s): b41f972

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +379 -359
app.py CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  import os
2
  import sys
3
  import gc
@@ -11,203 +15,148 @@ from typing import Dict, Optional, Set, List, Any
11
  from collections import deque
12
  from contextlib import asynccontextmanager
13
  import tempfile
14
- import telegram
15
-
16
- # تنظیمات اولیه و مسیرها
17
- BASE_DIR = Path(__file__).parent
18
- temp_cache_dir = tempfile.gettempdir()
19
-
20
- # تنظیم متغیرهای محیطی قبل از import کردن کتابخانه‌های هوش مصنوعی
21
- os.environ["HF_HOME"] = str(temp_cache_dir)
22
- if "TRANSFORMERS_CACHE" in os.environ:
23
- del os.environ["TRANSFORMERS_CACHE"]
24
- os.environ["TRANSFORMERS_PARALLELISM"] = "false"
25
- os.environ["TORCH_HOME"] = str(Path(temp_cache_dir) / "torch")
26
 
27
- # کتابخانه‌های هوش مصنوعی
28
  import torch
29
  import transformers
30
- from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM, AutoModelForMaskedLM
31
-
32
- # کتابخانه‌های وب و تلگرام
33
- import aiofiles
34
- import aiodns
35
- import httpx
36
- import uvicorn
37
- from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request, HTTPException, Response, Query
38
- from fastapi.responses import HTMLResponse, FileResponse
39
  from fastapi.staticfiles import StaticFiles
40
  from fastapi.templating import Jinja2Templates
41
- from telegram import Update, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton, BotCommand, Bot
42
- from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, ContextTypes, filters, ApplicationBuilder
43
- from telegram.error import NetworkError
44
-
45
- # کتابخانه‌های دیگر
46
  import hazm
47
- from logging.handlers import RotatingFileHandler
48
- from cachetools import TTLCache
49
 
50
- # تنظیمات برنامه
51
- class AppConfig:
52
  """تنظیمات اصلی برنامه"""
53
-
 
 
 
 
 
 
54
  # تنظیمات مسیرها
55
  BASE_DIR = Path(__file__).parent
56
- STATIC_DIR = BASE_DIR / "static"
57
- TEMPLATES_DIR = BASE_DIR / "templates"
58
- CACHE_DIR = Path(temp_cache_dir) / "cache"
59
- LOG_DIR = Path(temp_cache_dir) / "logs"
60
-
61
- # تنظیمات تلگرام
62
- TELEGRAM_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN" # توکن ربات تلگرام خود را اینجا وارد کنید
63
- CHAT_ID = "YOUR_CHAT_ID" # شناسه چت خود را اینجا وارد کنید
64
- ADMIN_CHAT_ID = "YOUR_ADMIN_CHAT_ID" # شناسه ادمین خود را اینجا وارد کنید
65
- ALLOWED_CHAT_IDS = {CHAT_ID, ADMIN_CHAT_ID} # در صورت نیاز، شناسه‌های مجاز را اینجا وارد کنید
66
-
67
  # تنظیمات مدل
68
  MODEL_NAME = "HooshvareLab/bert-fa-base-uncased"
69
  MAX_LENGTH = 512
70
  MIN_LENGTH = 20
71
  TEMPERATURE = 0.7
72
- TOP_K = 50
73
- TOP_P = 0.92
74
-
75
- # تنظیمات سیستم
76
- MAX_RETRIES = 3
77
- CACHE_TTL = 86400 # 24 ساعت
78
- MAX_CONNECTIONS = 5
79
- MEMORY_THRESHOLD = 85
80
- CLEANUP_THRESHOLD = 80
81
- MAX_MESSAGE_LENGTH = 1024
82
- MAX_THREADS = os.cpu_count() or 4
83
 
84
  @classmethod
85
- def setup_directories(cls) -> None:
86
- """ایجاد دایرکتوری‌های مورد نیاز"""
87
- try:
88
- for path in [cls.CACHE_DIR, cls.LOG_DIR, cls.STATIC_DIR, cls.TEMPLATES_DIR]:
89
- path.mkdir(exist_ok=True, parents=True)
90
- logging.info("دایرکتوری‌ها با موفقیت ایجاد شدند")
91
- except Exception as e:
92
- logging.error(f"خطا در ایجاد دایرکتوری‌ها: {e}")
93
- raise
 
 
 
 
 
 
 
 
 
 
94
 
95
- # تنظیمات لاگ
96
- def setup_logging() -> None:
97
- """راه‌اندازی سیستم لاگ"""
98
- logger = logging.getLogger()
99
- logger.setLevel(logging.INFO)
100
-
101
- formatter = logging.Formatter(
102
- '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
103
- )
104
-
105
- # لاگ در فایل
106
- file_handler = RotatingFileHandler(
107
- AppConfig.LOG_DIR / 'app.log',
108
- maxBytes=10 ** 6,
109
- backupCount=5,
110
- encoding='utf-8'
111
- )
112
- file_handler.setFormatter(formatter)
113
-
114
- # لاگ در کنسول
115
- console_handler = logging.StreamHandler(sys.stdout)
116
- console_handler.setFormatter(formatter)
117
-
118
- logger.handlers = []
119
- logger.addHandler(file_handler)
120
- logger.addHandler(console_handler)
121
-
122
- # مدل هوش مصنوعی
123
  class AIModel:
124
  """کلاس مدیریت مدل هوش مصنوعی"""
125
-
126
  def __init__(self):
127
- self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
128
  self.model = None
129
  self.tokenizer = None
130
  self.pipeline = None
131
  self._initialize_model()
132
-
133
  def _initialize_model(self):
 
134
  try:
135
- logging.info(f"در حال بارگذاری مدل {AppConfig.MODEL_NAME}...")
136
-
137
  # بارگذاری توکنایزر
138
  self.tokenizer = AutoTokenizer.from_pretrained(
139
- AppConfig.MODEL_NAME,
140
- cache_dir=AppConfig.CACHE_DIR,
141
  use_fast=True
142
  )
143
-
144
  # بارگذاری مدل
145
- if torch.cuda.is_available():
146
- self.model = AutoModelForCausalLM.from_pretrained(
147
- AppConfig.MODEL_NAME,
148
- cache_dir=AppConfig.CACHE_DIR,
149
- torch_dtype=torch.float16,
150
- low_cpu_mem_usage=True,
151
- )
152
- else:
153
- self.model = AutoModelForCausalLM.from_pretrained(
154
- AppConfig.MODEL_NAME,
155
- cache_dir=AppConfig.CACHE_DIR,
156
- low_cpu_mem_usage=True,
157
- )
158
- logging.warning("CUDA در دسترس نیست. استفاده از CPU ممکن است کند باشد.")
159
-
160
  # ایجاد پایپلاین
161
  self.pipeline = pipeline(
162
  "text-generation",
163
  model=self.model,
164
  tokenizer=self.tokenizer,
165
- device=0 if torch.cuda.is_available() else -1,
166
  )
167
-
168
  logging.info("مدل با موفقیت بارگذاری شد")
169
  return True
 
170
  except Exception as e:
171
  logging.error(f"خطا در بارگذاری مدل: {e}")
172
  return False
173
-
174
  async def generate_response(self, text: str) -> str:
 
175
  try:
176
- # پیش پردازش متن
177
- # ... (می‌توانید توابع پیش پردازش مانند حذف ایموجی، اعداد و غیره را اضافه کنید)
178
-
 
179
  # تولید پاسخ
180
- result = self.pipeline(
181
  text,
182
- max_length=AppConfig.MAX_LENGTH,
183
- min_length=AppConfig.MIN_LENGTH,
184
- temperature=AppConfig.TEMPERATURE,
185
- top_k=AppConfig.TOP_K,
186
- top_p=AppConfig.TOP_P,
187
  do_sample=True,
188
- no_repeat_ngram_size=3,
189
- pad_token_id=self.tokenizer.eos_token_id,
190
- num_return_sequences=1,
191
  )
192
-
193
- response = result[0]["generated_text"]
194
-
195
- # پس پردازش پاسخ
196
- response = response.replace(text, "").strip()
197
-
198
- return response
199
-
200
  except Exception as e:
201
  logging.error(f"خطا در تولید پاسخ: {e}")
202
- return "متأسفم، در تولید پاسخ مشکلی پیش آمد."
203
 
204
- # رابط کاربری تلگرام
205
  class TelegramUI:
206
  """کلاس مدیریت رابط کاربری تلگرام"""
207
-
208
  @staticmethod
209
  def get_main_menu() -> InlineKeyboardMarkup:
210
- """ایجاد منوی اصلی"""
211
  keyboard = [
212
  [
213
  InlineKeyboardButton("💬 چت جدید", callback_data="new_chat"),
@@ -225,204 +174,297 @@ class TelegramUI:
225
  return InlineKeyboardMarkup(keyboard)
226
 
227
  @staticmethod
228
- def get_main_menu_with_icons() -> InlineKeyboardMarkup:
229
- """ایجاد منوی اصلی با آیکون"""
230
  keyboard = [
231
  [
232
- InlineKeyboardButton("💬", callback_data="new_chat"),
233
- InlineKeyboardButton("🔍", callback_data="search")
234
  ],
235
  [
236
- InlineKeyboardButton("📚", callback_data="learn"),
237
- InlineKeyboardButton("🎯", callback_data="practice")
238
  ],
239
  [
240
- InlineKeyboardButton("⚙️", callback_data="settings"),
241
- InlineKeyboardButton("❓", callback_data="help")
242
  ]
243
  ]
244
  return InlineKeyboardMarkup(keyboard)
245
 
246
  @staticmethod
247
- def get_chat_keyboard() -> ReplyKeyboardMarkup:
248
- """ایجاد صفحه کلید چت"""
249
  keyboard = [
250
- ["💭 چت عمومی", "❓ سوال و جواب"],
251
- ["📚 آموزش", "🎯 تمرین"],
252
- ["🏠 منوی اصلی"]
 
 
 
 
 
 
 
 
253
  ]
254
- return ReplyKeyboardMarkup(keyboard, resize_keyboard=True, is_persistent=True)
255
 
256
- # ربات تلگرام
257
  class TelegramBot:
258
  """کلاس اصلی ربات تلگرام"""
259
-
260
  def __init__(self, ai_model: AIModel):
261
  self.model = ai_model
262
  self.ui = TelegramUI()
263
  self.application = (
264
- ApplicationBuilder()
265
- .token(AppConfig.TELEGRAM_TOKEN)
266
  .build()
267
  )
268
- self._setup_handlers()
269
  self.normalizer = hazm.Normalizer()
270
- self.message_queue = asyncio.Queue()
271
- asyncio.create_task(self.process_message_queue())
272
- self._set_my_commands()
273
 
274
- def _set_my_commands(self):
275
  """تنظیم دستورات ربات"""
276
- bot = Bot(AppConfig.TELEGRAM_TOKEN)
277
-
 
 
 
 
 
 
278
  async def set_commands():
279
- await bot.set_my_commands([
280
- BotCommand("help", "نمایش راهنما"),
281
- BotCommand("model", "نمایش اطلاعات مدل"),
282
- BotCommand("status", "نمایش وضعیت سرور"),
283
- ])
284
-
285
  asyncio.create_task(set_commands())
286
 
287
  def _setup_handlers(self):
288
  """تنظیم هندلرهای ربات"""
289
- self.application.add_handler(CallbackQueryHandler(self.button_callback))
290
- self.application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message))
291
  self.application.add_handler(CommandHandler("help", self.help_command))
292
- self.application.add_handler(CommandHandler("model", self.model_command))
293
- self.application.add_handler(CommandHandler("status", self.status_command))
 
 
 
 
 
 
 
 
 
294
 
295
- async def help_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
296
- """پردازش دستور راهنما"""
297
- help_text = """
298
- راهنمای استفاده از ربات:
299
 
300
- /help - نمایش راهنما
301
- /model - نمایش اطلاعات مدل
302
- /status - نمایش وضعیت سرور
 
303
 
304
- برای ارسال پیام، کافیست متن خود را تایپ کرده و ارسال کنید.
305
  """
306
- await update.message.reply_text(help_text)
307
-
308
- async def model_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
309
- """پردازش دستور نمایش اطلاعات مدل"""
310
- model_info = f"""
311
- مدل: {AppConfig.MODEL_NAME}
312
- حداکثر طول ورودی: {AppConfig.MAX_LENGTH}
313
- دمای تولید: {AppConfig.TEMPERATURE}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
314
  """
315
- await update.message.reply_text(model_info)
 
 
 
316
 
317
- async def status_command(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
318
- """پردازش دستور نمایش وضعیت سرور"""
319
- try:
320
- # بررسی وضعیت سرور
321
- status_text = "وضعیت سرور: نرمال"
322
- except Exception as e:
323
- logging.error(f"خطا در بررسی وضعیت سرور: {e}")
324
- status_text = "خطا در بررسی وضعیت سرور"
325
- await update.message.reply_text(status_text)
326
 
327
- async def button_callback(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
328
- """پردازش دکمه‌های منو"""
329
- query = update.callback_query
330
- await query.answer()
 
 
331
 
332
- if query.data == "new_chat":
333
- await query.message.reply_text(
334
- "💭 نوع چت را انتخاب کنید:",
335
- reply_markup=self.ui.get_chat_keyboard()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
336
  )
 
 
 
 
 
 
 
337
  elif query.data == "help":
338
  help_text = """
339
- 📚 راهنمای استفاده:
340
-
341
- 1️⃣ برای شروع گفتگو، متن خود را تایپ و ارسال کنید.
342
- 2️⃣ نوع گفتگو را انتخاب کنید
343
- 3️⃣ سؤال یا پیام خود را ارسال کنید
344
- 4️⃣ منتظر پاسخ بمانید
345
-
346
- ❗️ نکات مهم:
347
- • سؤالات خود را واضح و دقیق مطرح کنید
348
- از منوی تنظیمات برای شخصی‌سازی استفاده کنید
349
- برای بازگشت به منوی اصلی از دکمه "🏠 منوی اصلی" استفاده کنید
 
 
350
  """
351
- await query.message.edit_text(help_text, reply_markup=self.ui.get_main_menu_with_icons())
352
- elif query.data == "settings":
353
- await query.message.reply_text("⚙️ در حال حاضر تنظیمات خاصی در دسترس نیست.")
354
- elif query.data == "learn":
355
- await query.message.reply_text("📚 در حال حاضر محتوای آموزشی‌ای در دسترس نیست.")
356
- elif query.data == "practice":
357
- await query.message.reply_text("🎯 در حال حاضر تمرینی در دسترس نیست.")
358
- elif query.data == "search":
359
- await query.message.reply_text("🔍 در حال حاضر قابلیتی برای جستجو در دسترس نیست.")
360
- else:
361
- await query.message.edit_text("منوی اصلی:", reply_markup=self.ui.get_main_menu_with_icons())
362
-
363
- async def handle_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
364
- """پردازش پیام‌های دریافتی"""
365
- if update.message.text == "🏠 منوی اصلی":
366
- await update.message.reply_text("منوی اصلی:", reply_markup=self.ui.get_main_menu_with_icons())
367
- return
368
-
369
- await self.message_queue.put((update, context))
 
370
 
371
- async def process_message_queue(self):
372
- """پردازش صف پیام‌ها"""
373
- while True:
374
- update, context = await self.message_queue.get()
375
- try:
376
- # نرمال‌سازی متن
377
- normalized_text = self.normalizer.normalize(update.message.text)
378
-
379
- # بررسی طول پیام
380
- if len(normalized_text) > AppConfig.MAX_MESSAGE_LENGTH:
381
- await update.message.reply_text(
382
- "پیام شما بیش از حد طولانی است. لطفاً آن را کوتاه‌تر کنید."
383
- )
384
- continue
385
-
386
- # تولید پاسخ
387
- response = await self.model.generate_response(normalized_text)
388
-
389
- # ارسال پاسخ
390
- if len(response) > AppConfig.MAX_MESSAGE_LENGTH:
391
- for i in range(0, len(response), AppConfig.MAX_MESSAGE_LENGTH):
392
- await update.message.reply_text(f"🤖 {response[i:i+AppConfig.MAX_MESSAGE_LENGTH]}")
393
- else:
394
- await update.message.reply_text(f"🤖 {response}")
395
-
396
- except Exception as e:
397
- logging.error(f"خطا در پردازش پیام: {e}")
398
- await update.message.reply_text("متأسفانه خطایی رخ داد. لطفاً دوباره تلاش کنید.")
399
- finally:
400
- self.message_queue.task_done()
401
 
402
  async def run(self):
403
  """اجرای ربات"""
404
  try:
405
- self.application.run_polling(
406
- allowed_updates=Update.ALL_TYPES,
407
- drop_pending_updates=True
408
- )
409
-
410
- except NetworkError as e:
411
- logging.error(f"خطای شبکه: {e}")
412
- await asyncio.sleep(5) # صبر برای 5 ثانیه
413
- await self.run() # تلاش مجدد برای اتصال
414
  except Exception as e:
415
  logging.error(f"خطا در اجرای ربات: {e}")
416
  raise
417
 
418
- # برنامه FastAPI
419
- app = FastAPI(title="AI Assistant", lifespan=lifespan)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
420
 
421
- # پیوست کردن فایل‌های استاتیک
422
- app.mount("/static", StaticFiles(directory=AppConfig.STATIC_DIR), name="static")
423
- templates = Jinja2Templates(directory=AppConfig.TEMPLATES_DIR)
 
 
 
 
424
 
425
- # HTML پیش‌فرض
426
  DEFAULT_HTML = """
427
  <!DOCTYPE html>
428
  <html dir="rtl" lang="fa">
@@ -445,7 +487,6 @@ DEFAULT_HTML = """
445
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
446
  }
447
  h1 { color: #1a73e8; }
448
- .status { margin: 20px 0; }
449
  .chat-container {
450
  margin-top: 20px;
451
  border: 1px solid #e0e0e0;
@@ -458,30 +499,48 @@ DEFAULT_HTML = """
458
  <body>
459
  <div class="container">
460
  <h1>دستیار هوشمند</h1>
461
- <div class="status">
462
- <p>برای استفاده از دستیار هوشمند، به ربات تلگرام مراجعه کنید یا از رابط وب استفاده نمایید.</p>
463
- </div>
464
- <div class="chat-container" id="chat-container">
465
- </div>
466
- <input type="text" id="message-input" placeholder="پیام خود را بنویسید..." style="width: 100%; padding: 10px; margin-top: 10px;">
467
- <button onclick="sendMessage()" style="margin-top: 10px; padding: 10px 20px;">ارسال</button>
 
 
 
 
 
468
  </div>
 
469
  <script>
470
  const ws = new WebSocket(`ws://${window.location.host}/ws`);
471
  const chatContainer = document.getElementById('chat-container');
472
  const messageInput = document.getElementById('message-input');
473
-
474
- ws.onmessage = function(event) {
 
 
 
 
 
 
 
 
 
 
 
475
  const response = JSON.parse(event.data);
476
  appendMessage('bot', response.response);
477
  };
478
-
479
  function appendMessage(type, text) {
480
  const messageDiv = document.createElement('div');
 
481
  messageDiv.style.padding = '10px';
482
  messageDiv.style.margin = '5px';
483
  messageDiv.style.borderRadius = '5px';
484
-
485
  if (type === 'user') {
486
  messageDiv.style.backgroundColor = '#e3f2fd';
487
  messageDiv.style.marginLeft = '20%';
@@ -489,12 +548,12 @@ DEFAULT_HTML = """
489
  messageDiv.style.backgroundColor = '#f5f5f5';
490
  messageDiv.style.marginRight = '20%';
491
  }
492
-
493
  messageDiv.textContent = text;
494
  chatContainer.appendChild(messageDiv);
495
  chatContainer.scrollTop = chatContainer.scrollHeight;
496
  }
497
-
498
  function sendMessage() {
499
  const message = messageInput.value.trim();
500
  if (message) {
@@ -503,7 +562,7 @@ DEFAULT_HTML = """
503
  messageInput.value = '';
504
  }
505
  }
506
-
507
  messageInput.addEventListener('keypress', function(e) {
508
  if (e.key === 'Enter') {
509
  sendMessage();
@@ -514,85 +573,46 @@ DEFAULT_HTML = """
514
  </html>
515
  """
516
 
517
- @asynccontextmanager
518
- async def lifespan(app: FastAPI):
519
- """مدیریت چرخه حیات برنامه"""
520
- # راه‌اندازی
521
- AppConfig.setup_directories()
522
- setup_logging()
523
-
524
- # ایجاد نمونه از مدل هوش مصنوعی
525
- app.state.model = AIModel()
526
- app.state.is_ready = asyncio.Event()
527
-
528
- # راه‌اندازی ربات تلگرام
529
- app.state.bot = TelegramBot(app.state.model)
530
- asyncio.create_task(app.state.bot.run())
531
-
532
- yield
533
-
534
- # خاتمه
535
- tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
536
- for task in tasks:
537
- task.cancel()
538
- await asyncio.gather(*tasks, return_exceptions=True)
539
- await app.state.bot.application.shutdown()
540
- logging.info("برنامه با موفقیت خاتمه یافت")
541
-
542
  @app.get("/", response_class=HTMLResponse)
543
  async def home(request: Request):
544
  """صفحه اصلی"""
545
- try:
546
- # می‌توانید اطلاعات بیشتری را به تمپلیت ارسال کنید
547
- return templates.TemplateResponse(
548
- "index.html",
549
- {
550
- "request": request,
551
- "bot_status": "فعال" if request.app.state.is_ready.is_set() else "در حال آماده‌سازی"
552
- }
553
- )
554
- except Exception as e:
555
- logging.error(f"خطا در نمایش صفحه اصلی: {e}")
556
- return HTMLResponse(content=DEFAULT_HTML)
557
 
558
  @app.websocket("/ws")
559
  async def websocket_endpoint(websocket: WebSocket):
560
  """اتصال WebSocket"""
561
  await websocket.accept()
562
-
563
  try:
564
  while True:
565
  # دریافت پیام
566
  text = await websocket.receive_text()
567
-
568
  # تولید پاسخ
569
  response = await app.state.model.generate_response(text)
570
-
571
  # ارسال پاسخ
572
- await websocket.send_json({
573
- "response": response
574
- })
575
- except WebSocketDisconnect:
576
- logging.info("اتصال WebSocket قطع شد")
577
  except Exception as e:
578
  logging.error(f"خطا در WebSocket: {e}")
579
  await websocket.close()
580
 
581
- @app.exception_handler(404)
582
- async def not_found_handler(request: Request, exc: Exception):
583
- """پردازش خطای 404"""
584
- return HTMLResponse(content=DEFAULT_HTML, status_code=404)
585
-
586
- @app.exception_handler(500)
587
- async def server_error_handler(request: Request, exc: Exception):
588
- """پردازش خطای 500"""
589
- return HTMLResponse(content=DEFAULT_HTML, status_code=500)
590
 
591
  if __name__ == "__main__":
592
  # اجرای برنامه
593
  uvicorn.run(
594
- app,
595
- host="0.0.0.0",
596
- port=7860,
597
- log_level="info"
598
- )
 
1
+ """
2
+ دستیار هوشمند فارسی با قابلیت چت تلگرام و وب
3
+ """
4
+
5
  import os
6
  import sys
7
  import gc
 
15
  from collections import deque
16
  from contextlib import asynccontextmanager
17
  import tempfile
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
+ # تنظیم کتابخانه‌های اصلی
20
  import torch
21
  import transformers
22
+ from transformers import pipeline, AutoTokenizer, AutoModelForCausalLM
23
+ from fastapi import FastAPI, WebSocket, Request, HTTPException
24
+ from fastapi.responses import HTMLResponse
 
 
 
 
 
 
25
  from fastapi.staticfiles import StaticFiles
26
  from fastapi.templating import Jinja2Templates
27
+ import uvicorn
28
+ from telegram import Update, Bot, InlineKeyboardButton, InlineKeyboardMarkup, BotCommand
29
+ from telegram.ext import Application, CommandHandler, MessageHandler, CallbackQueryHandler, filters
 
 
30
  import hazm
 
 
31
 
32
+ class Config:
 
33
  """تنظیمات اصلی برنامه"""
34
+
35
+ # تنظیمات تلگرام
36
+ TELEGRAM_TOKEN = "7437859619:AAGeGG3ZkLM0OVaw-Exx1uMRE55JtBCZZCY"
37
+ CHAT_ID = "-1002228627548"AT_ID_HERE" # شناسه چت خودتون رو اینجا قرار بدید
38
+ ADMIN_CHAT_ID = "-1002228627548" # شناسه ادمین رو اینجا قرار بدید
39
+ ALLOWED_CHAT_IDS = {CHAT_ID, ADMIN_CHAT_ID} # چت‌های مجاز
40
+
41
  # تنظیمات مسیرها
42
  BASE_DIR = Path(__file__).parent
43
+ CACHE_DIR = Path(tempfile.gettempdir()) / "ai_assistant_cache"
44
+ LOG_DIR = Path(tempfile.gettempdir()) / "ai_assistant_logs"
45
+
 
 
 
 
 
 
 
 
46
  # تنظیمات مدل
47
  MODEL_NAME = "HooshvareLab/bert-fa-base-uncased"
48
  MAX_LENGTH = 512
49
  MIN_LENGTH = 20
50
  TEMPERATURE = 0.7
51
+ DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
52
+
53
+ # تنظیمات عمومی
54
+ DEBUG = True
55
+ HOST = "0.0.0.0"
56
+ PORT = 8000
 
 
 
 
 
57
 
58
  @classmethod
59
+ def setup(cls):
60
+ """راه‌اندازی اولیه تنظیمات"""
61
+ # ایجاد مسیرها
62
+ cls.CACHE_DIR.mkdir(parents=True, exist_ok=True)
63
+ cls.LOG_DIR.mkdir(parents=True, exist_ok=True)
64
+
65
+ # تنظیم متغیرهای محیطی
66
+ os.environ["TRANSFORMERS_CACHE"] = str(cls.CACHE_DIR)
67
+ os.environ["TORCH_HOME"] = str(cls.CACHE_DIR / "torch")
68
+
69
+ # تنظیم لاگینگ
70
+ logging.basicConfig(
71
+ level=logging.DEBUG if cls.DEBUG else logging.INFO,
72
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
73
+ handlers=[
74
+ logging.FileHandler(cls.LOG_DIR / "app.log", encoding='utf-8'),
75
+ logging.StreamHandler(sys.stdout)
76
+ ]
77
+ )
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  class AIModel:
80
  """کلاس مدیریت مدل هوش مصنوعی"""
81
+
82
  def __init__(self):
83
+ self.device = Config.DEVICE
84
  self.model = None
85
  self.tokenizer = None
86
  self.pipeline = None
87
  self._initialize_model()
88
+
89
  def _initialize_model(self):
90
+ """راه‌اندازی مدل"""
91
  try:
92
+ logging.info(f"بارگذاری مدل {Config.MODEL_NAME}...")
93
+
94
  # بارگذاری توکنایزر
95
  self.tokenizer = AutoTokenizer.from_pretrained(
96
+ Config.MODEL_NAME,
97
+ cache_dir=Config.CACHE_DIR,
98
  use_fast=True
99
  )
100
+
101
  # بارگذاری مدل
102
+ self.model = AutoModelForCausalLM.from_pretrained(
103
+ Config.MODEL_NAME,
104
+ cache_dir=Config.CACHE_DIR,
105
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
106
+ low_cpu_mem_usage=True
107
+ )
108
+
109
+ # انتقال مدل به GPU در صورت وجود
110
+ self.model.to(self.device)
111
+
 
 
 
 
 
112
  # ایجاد پایپلاین
113
  self.pipeline = pipeline(
114
  "text-generation",
115
  model=self.model,
116
  tokenizer=self.tokenizer,
117
+ device=0 if self.device == "cuda" else -1
118
  )
119
+
120
  logging.info("مدل با موفقیت بارگذاری شد")
121
  return True
122
+
123
  except Exception as e:
124
  logging.error(f"خطا در بارگذاری مدل: {e}")
125
  return False
126
+
127
  async def generate_response(self, text: str) -> str:
128
+ """تولید پاسخ برای متن ورودی"""
129
  try:
130
+ # نرمال‌سازی متن
131
+ normalizer = hazm.Normalizer()
132
+ text = normalizer.normalize(text)
133
+
134
  # تولید پاسخ
135
+ response = self.pipeline(
136
  text,
137
+ max_length=Config.MAX_LENGTH,
138
+ min_length=Config.MIN_LENGTH,
139
+ temperature=Config.TEMPERATURE,
 
 
140
  do_sample=True,
141
+ no_repeat_ngram_size=3
 
 
142
  )
143
+
144
+ # استخراج و پاکسازی پاسخ
145
+ generated_text = response[0]["generated_text"]
146
+ cleaned_response = generated_text.replace(text, "").strip()
147
+
148
+ return cleaned_response
149
+
 
150
  except Exception as e:
151
  logging.error(f"خطا در تولید پاسخ: {e}")
152
+ return "متأسفانه در تولید پاسخ مشکلی پیش آمد."
153
 
 
154
  class TelegramUI:
155
  """کلاس مدیریت رابط کاربری تلگرام"""
156
+
157
  @staticmethod
158
  def get_main_menu() -> InlineKeyboardMarkup:
159
+ """منوی اصلی با دکمه‌های تخت"""
160
  keyboard = [
161
  [
162
  InlineKeyboardButton("💬 چت جدید", callback_data="new_chat"),
 
174
  return InlineKeyboardMarkup(keyboard)
175
 
176
  @staticmethod
177
+ def get_chat_menu() -> InlineKeyboardMarkup:
178
+ """منوی چت"""
179
  keyboard = [
180
  [
181
+ InlineKeyboardButton("💭 چت عمومی", callback_data="general_chat"),
182
+ InlineKeyboardButton("❓ سوال و جواب", callback_data="qa_chat")
183
  ],
184
  [
185
+ InlineKeyboardButton("📝 ترجمه متن", callback_data="translate"),
186
+ InlineKeyboardButton("🔄 تصحیح متن", callback_data="correct_text")
187
  ],
188
  [
189
+ InlineKeyboardButton("🏠 بازگشت به منوی اصلی", callback_data="main_menu")
 
190
  ]
191
  ]
192
  return InlineKeyboardMarkup(keyboard)
193
 
194
  @staticmethod
195
+ def get_settings_menu() -> InlineKeyboardMarkup:
196
+ """منوی تنظیمات"""
197
  keyboard = [
198
+ [
199
+ InlineKeyboardButton("🔊 تنظیمات زبان", callback_data="lang_settings"),
200
+ InlineKeyboardButton("⚡️ حالت پاسخگویی", callback_data="response_mode")
201
+ ],
202
+ [
203
+ InlineKeyboardButton("📊 آمار عملکرد", callback_data="stats"),
204
+ InlineKeyboardButton("👤 پروفایل", callback_data="profile")
205
+ ],
206
+ [
207
+ InlineKeyboardButton("🏠 بازگشت به منوی اصلی", callback_data="main_menu")
208
+ ]
209
  ]
210
+ return InlineKeyboardMarkup(keyboard)
211
 
 
212
  class TelegramBot:
213
  """کلاس اصلی ربات تلگرام"""
214
+
215
  def __init__(self, ai_model: AIModel):
216
  self.model = ai_model
217
  self.ui = TelegramUI()
218
  self.application = (
219
+ Application.builder()
220
+ .token(Config.TELEGRAM_TOKEN)
221
  .build()
222
  )
 
223
  self.normalizer = hazm.Normalizer()
224
+ self._setup_handlers()
225
+ self.setup_commands()
 
226
 
227
+ def setup_commands(self):
228
  """تنظیم دستورات ربات"""
229
+ commands = [
230
+ BotCommand("start", "شروع گفتگو با ربات"),
231
+ BotCommand("help", "راهنمای استفاده"),
232
+ BotCommand("menu", "نمایش منوی اصلی"),
233
+ BotCommand("settings", "تنظیمات"),
234
+ BotCommand("stats", "آمار عملکرد"),
235
+ ]
236
+
237
  async def set_commands():
238
+ bot = Bot(Config.TELEGRAM_TOKEN)
239
+ await bot.set_my_commands(commands)
240
+
 
 
 
241
  asyncio.create_task(set_commands())
242
 
243
  def _setup_handlers(self):
244
  """تنظیم هندلرهای ربات"""
245
+ # دستورات اصلی
246
+ self.application.add_handler(CommandHandler("start", self.start_command))
247
  self.application.add_handler(CommandHandler("help", self.help_command))
248
+ self.application.add_handler(CommandHandler("menu", self.menu_command))
249
+ self.application.add_handler(CommandHandler("settings", self.settings_command))
250
+ self.application.add_handler(CommandHandler("stats", self.stats_command))
251
+
252
+ # هندلر دکمه‌ها
253
+ self.application.add_handler(CallbackQueryHandler(self.handle_callback))
254
+
255
+ # هندلر پیام‌های متنی
256
+ self.application.add_handler(
257
+ MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)
258
+ )
259
 
260
+ async def start_command(self, update: Update, context):
261
+ """پردازش دستور /start"""
262
+ welcome_text = """
263
+ 🌟 به دستیار هوشمند خوش آمدید!
264
 
265
+ من می‌تونم در موارد زیر کمکتون کنم:
266
+ پاسخ به سوالات شما
267
+ ترجمه و تصحیح متن
268
+ • آموزش و تمرین
269
 
270
+ برای شروع، از منوی زیر گزینه مورد نظر خود را انتخاب کنید:
271
  """
272
+ await update.message.reply_text(
273
+ welcome_text,
274
+ reply_markup=self.ui.get_main_menu()
275
+ )
276
+
277
+ async def help_command(self, update: Update, context):
278
+ """پردازش دستور /help"""
279
+ help_text = """
280
+ 📚 راهنمای استفاده از ربات:
281
+
282
+ 🔹 دستورات اصلی:
283
+ /start - شروع مجدد ربات
284
+ /menu - نمایش منوی اصلی
285
+ /help - نمایش این راهنما
286
+ /settings - تنظیمات ربات
287
+ /stats - مشاهده آمار
288
+
289
+ 🔹 نحوه استفاده:
290
+ 1. از منوی اصلی بخش مورد نظر را انتخاب کنید
291
+ 2. در بخش چت، سوال یا متن خود را وارد کنید
292
+ 3. منتظر پاسخ بمانید
293
+
294
+ 🔹 نکات مهم:
295
+ • سوالات خود را واضح و دقیق مطرح کنید
296
+ • برای بازگشت به منو از دکمه "🏠 بازگشت" استفاده کنید
297
  """
298
+ await update.message.reply_text(
299
+ help_text,
300
+ reply_markup=self.ui.get_main_menu()
301
+ )
302
 
303
+ async def menu_command(self, update: Update, context):
304
+ """نمایش منوی اصلی"""
305
+ await update.message.reply_text(
306
+ "🏠 منوی اصلی:",
307
+ reply_markup=self.ui.get_main_menu()
308
+ )
 
 
 
309
 
310
+ async def settings_command(self, update: Update, context):
311
+ """نمایش منوی تنظیمات"""
312
+ await update.message.reply_text(
313
+ "⚙️ تنظیمات:",
314
+ reply_markup=self.ui.get_settings_menu()
315
+ )
316
 
317
+ async def stats_command(self, update: Update, context):
318
+ """نمایش آمار"""
319
+ stats_text = """
320
+ 📊 آمار استفاده از ربات:
321
+ • تعداد پیام‌های دریافتی: 0
322
+ • تعداد پاسخ‌های ارسالی: 0
323
+ • زمان آنلاین بودن: 0 ساعت
324
+ """
325
+ await update.message.reply_text(stats_text)
326
+
327
+ async def handle_callback(self, update: Update, context):
328
+ """پردازش دکمه‌های فشرده شده"""
329
+ query = update.callback_query
330
+ await query.answer() # پاسخ به کلیک دکمه
331
+
332
+ if query.data == "main_menu":
333
+ await query.message.edit_text(
334
+ "🏠 منوی اصلی:",
335
+ reply_markup=self.ui.get_main_menu()
336
+ )
337
+
338
+ elif query.data == "new_chat":
339
+ await query.message.edit_text(
340
+ "💭 لطفاً نوع چت را انتخاب کنید:",
341
+ reply_markup=self.ui.get_chat_menu()
342
  )
343
+
344
+ elif query.data == "settings":
345
+ await query.message.edit_text(
346
+ "⚙️ تنظیمات:",
347
+ reply_markup=self.ui.get_settings_menu()
348
+ )
349
+
350
  elif query.data == "help":
351
  help_text = """
352
+ 📚 راهنمای استفاده از ربات:
353
+
354
+ 🔹 دستورات اصلی:
355
+ /start - شروع مجدد ربات
356
+ /menu - نمایش منوی اصلی
357
+ /help - نمایش این راهنما
358
+ /settings - تنظیمات ربات
359
+ /stats - مشاهده آمار
360
+
361
+ 🔹 نحوه استفاده:
362
+ 1. از منوی اصلی بخش مورد نظر را انتخاب کنید
363
+ 2. در بخش چت، سوال یا متن خود را وارد کنید
364
+ 3. منتظر پاسخ بمانید
365
  """
366
+ await query.message.edit_text(
367
+ help_text,
368
+ reply_markup=self.ui.get_main_menu()
369
+ )
370
+
371
+ elif query.data in ["general_chat", "qa_chat"]:
372
+ chat_type = "عمومی" if query.data == "general_chat" else "سوال و جواب"
373
+ await query.message.edit_text(
374
+ f"💬 وارد بخش چت {chat_type} شدید.\nلطفاً پیام خود را بنویسید..."
375
+ )
376
+
377
+ elif query.data == "translate":
378
+ await query.message.edit_text(
379
+ "📝 لطفاً متن مورد نظر برای ترجمه را وارد کنید..."
380
+ )
381
+
382
+ elif query.data == "correct_text":
383
+ await query.message.edit_text(
384
+ "🔄 لطفاً متن مورد نظر برای تصحیح را وارد کنید..."
385
+ )
386
 
387
+ async def handle_message(self, update: Update, context):
388
+ """پردازش پیام‌های دریافتی"""
389
+ try:
390
+ # بررسی دسترسی کاربر
391
+ if str(update.message.chat_id) not in Config.ALLOWED_CHAT_IDS:
392
+ await update.message.reply_text(
393
+ "⚠️ شما مجوز استفاده از این ربات را ندارید."
394
+ )
395
+ return
396
+
397
+ # نرمال‌سازی متن
398
+ text = self.normalizer.normalize(update.message.text)
399
+
400
+ # دریافت پاسخ از مدل
401
+ response = await self.model.generate_response(text)
402
+
403
+ # ارسال پاسخ با دکمه برگشت به منو
404
+ keyboard = [[InlineKeyboardButton("🏠 بازگشت به منو", callback_data="main_menu")]]
405
+ reply_markup = InlineKeyboardMarkup(keyboard)
406
+
407
+ await update.message.reply_text(
408
+ f"🤖 {response}",
409
+ reply_markup=reply_markup
410
+ )
411
+
412
+ except Exception as e:
413
+ logging.error(f"خطا در پردازش پیام: {e}")
414
+ await update.message.reply_text(
415
+ "❌ متأسفانه در پردازش پیام مشکلی پیش آمد. لطفاً دوباره تلاش کنید."
416
+ )
417
 
418
  async def run(self):
419
  """اجرای ربات"""
420
  try:
421
+ await self.application.initialize()
422
+ await self.application.start()
423
+ await self.application.run_polling()
424
+
 
 
 
 
 
425
  except Exception as e:
426
  logging.error(f"خطا در اجرای ربات: {e}")
427
  raise
428
 
429
+ @asynccontextmanager
430
+ async def lifespan(app: FastAPI):
431
+ """مدیریت چرخه حیات برنامه"""
432
+ # راه‌اندازی
433
+ Config.setup()
434
+
435
+ # ایجاد نمونه از مدل
436
+ app.state.model = AIModel()
437
+ app.state.is_ready = asyncio.Event()
438
+
439
+ # راه‌اندازی ربات تلگرام
440
+ app.state.bot = TelegramBot(app.state.model)
441
+ asyncio.create_task(app.state.bot.run())
442
+
443
+ # آماده‌سازی کامل شد
444
+ app.state.is_ready.set()
445
+
446
+ yield
447
+
448
+ # خاتمه برنامه
449
+ tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
450
+ for task in tasks:
451
+ task.cancel()
452
+ await asyncio.gather(*tasks, return_exceptions=True)
453
+
454
+ if hasattr(app.state, "bot"):
455
+ await app.state.bot.application.stop()
456
+
457
+ logging.info("برنامه با موفقیت خاتمه یافت")
458
 
459
+ # ساخت نمونه FastAPI
460
+ app = FastAPI(
461
+ title="دستیار هوشمند",
462
+ description="API دستیار هوشمند مبتنی بر هوش مصنوعی",
463
+ version="1.0.0",
464
+ lifespan=lifespan
465
+ )
466
 
467
+ # محتوای پیش‌فرض HTML
468
  DEFAULT_HTML = """
469
  <!DOCTYPE html>
470
  <html dir="rtl" lang="fa">
 
487
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
488
  }
489
  h1 { color: #1a73e8; }
 
490
  .chat-container {
491
  margin-top: 20px;
492
  border: 1px solid #e0e0e0;
 
499
  <body>
500
  <div class="container">
501
  <h1>دستیار هوشمند</h1>
502
+ <div id="status"></div>
503
+ <div class="chat-container" id="chat-container"></div>
504
+ <input
505
+ type="text"
506
+ id="message-input"
507
+ placeholder="پیام خود را بنویسید..."
508
+ style="width: 100%; padding: 10px; margin-top: 10px;">
509
+ <button
510
+ onclick="sendMessage()"
511
+ style="margin-top: 10px; padding: 10px 20px;">
512
+ ارسال
513
+ </button>
514
  </div>
515
+
516
  <script>
517
  const ws = new WebSocket(`ws://${window.location.host}/ws`);
518
  const chatContainer = document.getElementById('chat-container');
519
  const messageInput = document.getElementById('message-input');
520
+ const statusDiv = document.getElementById('status');
521
+
522
+ ws.onopen = () => {
523
+ statusDiv.textContent = 'وضعیت: متصل';
524
+ statusDiv.style.color = 'green';
525
+ };
526
+
527
+ ws.onclose = () => {
528
+ statusDiv.textContent = 'وضعیت: قطع شده';
529
+ statusDiv.style.color = 'red';
530
+ };
531
+
532
+ ws.onmessage = (event) => {
533
  const response = JSON.parse(event.data);
534
  appendMessage('bot', response.response);
535
  };
536
+
537
  function appendMessage(type, text) {
538
  const messageDiv = document.createElement('div');
539
+ messageDiv.className = `message ${type}`;
540
  messageDiv.style.padding = '10px';
541
  messageDiv.style.margin = '5px';
542
  messageDiv.style.borderRadius = '5px';
543
+
544
  if (type === 'user') {
545
  messageDiv.style.backgroundColor = '#e3f2fd';
546
  messageDiv.style.marginLeft = '20%';
 
548
  messageDiv.style.backgroundColor = '#f5f5f5';
549
  messageDiv.style.marginRight = '20%';
550
  }
551
+
552
  messageDiv.textContent = text;
553
  chatContainer.appendChild(messageDiv);
554
  chatContainer.scrollTop = chatContainer.scrollHeight;
555
  }
556
+
557
  function sendMessage() {
558
  const message = messageInput.value.trim();
559
  if (message) {
 
562
  messageInput.value = '';
563
  }
564
  }
565
+
566
  messageInput.addEventListener('keypress', function(e) {
567
  if (e.key === 'Enter') {
568
  sendMessage();
 
573
  </html>
574
  """
575
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
576
  @app.get("/", response_class=HTMLResponse)
577
  async def home(request: Request):
578
  """صفحه اصلی"""
579
+ return HTMLResponse(content=DEFAULT_HTML)
 
 
 
 
 
 
 
 
 
 
 
580
 
581
  @app.websocket("/ws")
582
  async def websocket_endpoint(websocket: WebSocket):
583
  """اتصال WebSocket"""
584
  await websocket.accept()
585
+
586
  try:
587
  while True:
588
  # دریافت پیام
589
  text = await websocket.receive_text()
590
+
591
  # تولید پاسخ
592
  response = await app.state.model.generate_response(text)
593
+
594
  # ارسال پاسخ
595
+ await websocket.send_json({"response": response})
596
+
 
 
 
597
  except Exception as e:
598
  logging.error(f"خطا در WebSocket: {e}")
599
  await websocket.close()
600
 
601
+ @app.get("/health")
602
+ async def health_check():
603
+ """بررسی سلامت برنامه"""
604
+ return {
605
+ "status": "ok",
606
+ "timestamp": datetime.utcnow().isoformat(),
607
+ "model_loaded": hasattr(app.state, "model"),
608
+ "bot_running": hasattr(app.state, "bot")
609
+ }
610
 
611
  if __name__ == "__main__":
612
  # اجرای برنامه
613
  uvicorn.run(
614
+ "app:app",
615
+ host=Config.HOST,
616
+ port=Config.PORT,
617
+ reload=Config.DEBUG
618
+ )