diff --git a/nahj_engine_general_runner.py b/nahj_engine_general_runner.py new file mode 100644 index 0000000..a0199fe --- /dev/null +++ b/nahj_engine_general_runner.py @@ -0,0 +1,309 @@ +import json +from fastapi import FastAPI, Request +from pydantic import BaseModel +import requests +import logging +import uvicorn +import random +import time +import nahj_engine as nahj_chat +import data_model as dm +# =========================== +# پیکربندی اولیه +# =========================== +TOKEN = "602738113:OcVhjcsXqvE6D9FUytdoMZ096DPKYIUwnrk" +API_URL = f"https://tapi.bale.ai/bot{TOKEN}/" + +# راه‌اندازی لاگر +logging.basicConfig( + filename="./bale_bot/bot.log", + level=logging.INFO, + format="%(asctime)s - %(levelname)s - %(message)s" +) + +# =========================== +# define import model class +# =========================== +class Message(BaseModel): + chat: dict + text: str | None = None + +class Update(BaseModel): + message: Message | None = None + + +async def get_latest_req_id(self): + + latest_request = dm.get_last_request() + latest_req_id = latest_request['update_id'] + if not latest_req_id: + latest_req_id = 0 + + return latest_req_id + 1 + +async def save_entery(self, update_item): + is_active = True + answer = '' + message = update_item['message'] + fromm = message['from'] + chat = message['chat'] + username, first_name, last_name = '','','' + if 'username' in fromm: + username = fromm['username'] + if 'first_name' in fromm: + first_name = fromm['first_name'] + if 'last_name' in fromm: + last_name = fromm['last_name'] + try: + dm.insert_request(update_item['update_id'],username,message['text'], answer, message['message_id'],fromm['id'],fromm['is_bot'],message['date'],chat['id'],chat['type'],first_name,last_name, is_active) + except: + return update_item['update_id'] + + return update_item['update_id'] + +async def update_request(self, update_id, answer): + dm.update_request(update_id= update_id, answer= answer) + + +async def split_text_into_chunks(self, text, max_length=4000): + """ + تقسیم یک متن به چانک‌های حداکثر max_length کاراکتری، بدون خراب کردن معنا با رعایت انتهای جمله‌ها. + + :param text: متن ورودی + :param max_length: حداکثر طول هر چانک (پیش‌فرض 4000) + :return: لیستی از چانک‌های متن + """ + chunks = [] # لیستی برای ذخیره چانک‌ها + start = 0 # شروع متن برای هر چانک + + while start < len(text): + # اگر متن باقی‌مانده کوتاه‌تر از max_length باشد، کل آن را اضافه کنید + if len(text) - start <= max_length: + chunks.append(text[start:]) + break + + # پیدا کردن نقطه پایانی چانک (حداکثر تا max_length کاراکتر جلو بروید) + end = start + max_length + # اگر در وسط یک جمله هستیم، به عقب برگردید تا انتهای جمله پیدا شود + while end > start and text[end - 1] not in '.!?': + end -= 1 + + # اگر هیچ انتهای جمله پیدا نشد، متن را تا max_length ببرید + if end == start: + end = start + max_length + + # اضافه کردن چانک به لیست + chunks.append(text[start:end]) + # شروع چانک بعدی + start = end + + return chunks + +async def save_chat_data(self,query, answer, first_name, username): + chat_data = f'''username: {username}\nfirstname: {first_name}\nquery: {query}\nanswer:{answer}\n+ + + + + + + + + + + + + + + + + + + + \n+ + + + + + + + + + + + + + + + + + + + \n\n''' + # # should write in DATABASE + with open('./bale_bot/chat-data.txt', 'a+', encoding='utf-8') as file: + file.write(chat_data) + +async def handle_update(self, update_reqs: dict): + print(f"handle update ...") + data = update_reqs + if "message" not in data: + return + message = data["message"] + chat_id = message["chat"]["id"] + text = message.get("text", "").strip() + + fromm = message['from'] + # first_name = fromm['first_name'] + # username = fromm['username'] + + logging.info(f"Received message from {chat_id}: {text}") + + keyboard = { + "keyboard": [["جستجو","پرسش","پرسش عمیق"]],# ,"شبکه معنایی" + "resize_keyboard": True, + "one_time_keyboard": True + } + + if text == "/start": + reply = "سلام، من دستیار هوشمند نهج‌البلاغه هستم. لطفا یکی از گزینه‌های زیر را انتخاب نمائید ..." + + await self.send_message(chat_id, reply, keyboard) + return + + elif text == "پرسش": + # حذف نوع درخواست قبلی کاربر + self.user_states.pop(chat_id, None) + # ایجاد وضعیت پرسش برای کاربر جاری + self.user_states[chat_id] = "simple_question" + reply = "لطفا متن «پرسش» را وارد نمائید ..." + await self.send_message(chat_id, reply, keyboard) + return + + elif text == "جستجو": + # حذف نوع درخواست قبلی کاربر + self.user_states.pop(chat_id, None) + # ایجاد وضعیت جستجو برای کاربر جاری + self.user_states[chat_id] = "search" + reply = "لطفا متن موردنظر جهت «جستجو» را وارد نمائید ..." + await self.send_message(chat_id, reply, keyboard) + return + + elif text == "شبکه معنایی": + # حذف نوع درخواست قبلی کاربر + self.user_states.pop(chat_id, None) + # ایجاد وضعیت شبکه معنایی برای کاربر جاری + self.user_states[chat_id] = "semantic-network" + reply = "لطفا کلمه موردنظر جهت ترسیم «شبکه معنایی» را وارد نمائید ..." + await self.send_message(chat_id, reply, keyboard) + return + + elif text == "پرسش عمیق": + # حذف نوع درخواست قبلی کاربر + self.user_states.pop(chat_id, None) + # ایجاد وضعیت پرسش برای کاربر جاری + self.user_states[chat_id] = "deep_question" + reply = "لطفا متن «پرسش عمیق» را وارد نمائید ..." + await self.send_message(chat_id, reply, keyboard) + return + + # elif text == "/help": + # reply = ( + # "دستورهای موجود:\n" + # "/start - شروع ربات\n" + # "/chat - گفت‌گو با دستیار هوشمند نهج البلاغه\n" + # "/status - وضعیت ربات" + # ) + # self.send_message(chat_id, reply) + + elif text == "ربات": + reply = "ربات فعال است ✅" + await self.send_message(chat_id, reply, keyboard) + return + + elif self.user_states.get(chat_id) == "semantic-network": + await self.send_message(chat_id, f"⏳ در حال ایجاد شبکه معنایی برای کلمه «{text}» ...") + reply = 'با عرض پوزش؛ این امکان، در حال حاضر در دسترس نیست' + + elif self.user_states.get(chat_id) == "search": + await self.send_message(chat_id, f"⏳ در حال جستجو برای «{text}» ...") + + answer = nahj_chat.bale_search(text) + + if answer: + reply = answer + else: + reply = 'خطا در تولید پاسخ!' + + elif self.user_states.get(chat_id) == "simple_question": + + await self.send_message(chat_id, f"⏳ در حال آماده‌سازی پاسخ به «{text}» ...") + + answer = nahj_chat.bale_chat(text) + + if answer: + reply = answer + else: + reply = 'خطا در تولید پاسخ!' + + elif self.user_states.get(chat_id) == "deep_question": + + await self.send_message(chat_id, f"⏳ در حال آماده‌سازی پاسخ به «{text}» ...") + + # answer = nahj_chat.bale_chat(text) + final_result = await nahj_chat.bale_complex_chat(text) + + if final_result: + + sub_questions = 'سوالات جزئی مرتبط با سوال کاربر:\n' + for i, q in enumerate(final_result.get('sub_qa'),1): + sub_questions += f'{i}. {q.get("question")}\n' + + sub_qa_text = '' + for i, qa in enumerate(final_result.get('sub_qa'),1): + sub_qa_text += f'{i}. {qa.get("question")}\n{qa.get("answer")}\n\n' + # reply_content = f'''سوال اصلی: {text}\n\n{sub_questions}\n\n* * * * *سوالات جزئی:\n{sub_qa_text.strip()}\n\nپاسخ نهائی:\n{final_result.get('final_answer',0)}''' + reply_content = f'''{final_result.get('final_answer',0)}''' + reply = reply_content.strip() + + else: + reply = 'خطا در تولید پاسخ!' + + else: + reply = "لطفا یکی از گزینه‌های زیر را انتخاب نمائید" + await self.send_message(chat_id, reply, keyboard) + return + + reply_len = len(reply.split()) + print(f"len answer: {reply_len}") + print(f"ready for next ...") + print('+'*20) + print('+'*20) + reply_chuncs = [] + + reply_chuncs = await self.split_text_into_chunks(reply) + + for i, paragraph in enumerate(reply_chuncs): + await self.send_message(chat_id, paragraph, keyboard) + + # await self.save_chat_data(text, reply, first_name, username) + return reply + +# =========================== +# ساخت اپلیکیشن FastAPI +# =========================== +app = FastAPI() + +@app.post("/chat") +async def chat(request: Request): + """ + دریافت مستقیم آبجکت ورودی به صورت JSON + """ + # دریافت بدنه درخواست + body = await request.json() + + # دسترسی به فیلدها + user_input = body.get("message") + metadata = body.get("metadata", {}) + + # update_id = await save_entery(item) + update_id = random.randint(1, 10) + answer = nahj_chat.bale_chat(user_input) + + if not answer: + reply = 'خطا در تولید پاسخ!' + + if answer: + await update_request(update_id, answer) + + # برگرداندن آبجکت خروجی (خودکار به JSON تبدیل می‌شود) + return { + "output": answer, + "status": "ok", + "input_received": user_input + } + +print(f'%%%%%%%%%%%%%%%%%%%%%%%%%%%%') +print(f'!!! NAHJ-RUNNER IS READY !!!') +print(f'%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + +# =========================== +# (local execution) +# =========================== + +# if __name__ == "__main__": +# import asyncio +# result = asyncio.run(chat()) + +if __name__ == "__main__": + uvicorn.run(app, host="127.0.0.1", port=8010) + # uvicorn.run( + # "nahj_engine_general_runner:app", + # host="0.0.0.0", + # port=8010, + # reload=True, # فعال بودن reload برای دیباگ مفید است + # log_level="debug" + # ) + +# uvicorn nahj_engine_general_runner:app --reload --host 0.0.0.0 --port 8010 \ No newline at end of file