From 6acabcf52d0022f474f1dd159243c62ea7ab1a33 Mon Sep 17 00:00:00 2001 From: hsafaei Date: Sun, 1 Feb 2026 09:04:29 +0000 Subject: [PATCH] untill reason button --- core/core.py | 147 ++++- core/operation.py | 4 +- core/static.py | 41 +- inProcees_md.txt | 271 +++++++++ main.py | 8 +- proje_information.md | 83 ++- requierments.txt | 3 +- router/bale/bale.py | 4 +- router/bale/bale_buttons.py | 22 +- router/bale/bale_handle.py | 1128 ++++++++++++++++++++++++++--------- router/bale/base_model.py | 121 +++- router/bale/config.py | 58 +- 12 files changed, 1518 insertions(+), 372 deletions(-) create mode 100644 inProcees_md.txt diff --git a/core/core.py b/core/core.py index e9bb9f6..0637b75 100755 --- a/core/core.py +++ b/core/core.py @@ -369,10 +369,11 @@ class Formatter: self, _input: Union[List[RuleRelation], str], header="نتایج اولیه مغایرت های احتمالی :\n", + end_button:List=[], ): if isinstance(_input, str): _input = self.form_law_chat(_input) - return _input, [], [] + return _input, [], else: chunks = [] buttons = [] @@ -384,19 +385,20 @@ class Formatter: groups[title].add(item.db_rule.section_id) current = header - for idx, (qanon_title, section_ids) in enumerate(groups.items(), start=1): + for idx, (qanon_title, sections_data) in enumerate(groups.items(), start=1): + block_lines = [f"{self.number(idx)} در قانون {self.bold(qanon_title)}"] sample_items_by_section = {} for item in _input: if ( item.db_rule.qanon_title == qanon_title - and item.db_rule.section_id in section_ids + and item.db_rule.section_id in sections_data ): sid = item.db_rule.section_id if sid not in sample_items_by_section: sample_items_by_section[sid] = item - for sub_idx, section_id in enumerate(sorted(section_ids), start=1): + for sub_idx, section_id in enumerate(sorted(sections_data), start=1): item = sample_items_by_section[section_id] # representative item link = self.__make_link_qs(src=section_id) @@ -451,7 +453,91 @@ class Formatter: for i in v: mapping_data[k].append(input_dict[i]) + for i in end_button: + buttons.append(i) + return chunks, buttons, mapping_data + + async def structed_form_subject_unity( + self, + _input: Union[List[RuleRelation], str], + header="نتایج بررسی وحدت موضوعی :\n", + end_button:List=[], + ): + """ + خروجی به صورت ساختارمند : + [پیام-دکمه], ... + """ + if isinstance(_input, str): + _input = self.form_law_chat(_input) + return _input, [], [] + else: + chunks_data = [] + groups_data = defaultdict(set) + main_idx = 1 + current = header + + # گروه بندی بر اساس عنوان قانون + for item in _input: + qanon_id = item.db_rule.qanon_id + + is_unity = item.subject_unity.has_subject_unity + if is_unity != 'no': + groups_data[qanon_id].add( + item + ) + + for _idx, (qanon_id, rule_data) in enumerate(groups_data.items(), start=1): + # for idx, (qanon_title, rule_data) in enumerate(groups.items(), start=1): + for _idx2, single_data in enumerate(rule_data, start=1): + if _idx2 == 1: + qanon_title = single_data.db_rule.qanon_title + block_lines = [self.bold(current)] + block_lines += [f" در قانون {self.bold(qanon_title)}"] + + link = self.__make_link_qs(src=single_data.db_rule.section_id) + + unity = single_data.subject_unity + if not unity: + block_lines.append("\t\t—") + continue + + if unity.has_subject_unity == "yes": + block_lines.append(f"توضیح {main_idx} بر اساس {link}:") + block_lines.append(f"\t{unity.reasoning or ''}") + + elif unity.has_subject_unity == "yes_under_assumptions": + block_lines.append(f"توضیح {main_idx} بر اساس {link}:") + block_lines.append(f"\t{unity.reasoning or ''}") + block_lines.append("\tتوضیحات بیشتر (فرضیات لازم):") + block_lines.append(f"\t{unity.required_assumptions or ''}") + + main_idx += 1 + + m_text = "\n".join(block_lines) + # Auto-chunk based on length + if len(m_text) > MAX_LEN: + # chunking m_text + pass + else: + print(f'structed_form_subject_unity; {len(m_text)}') + + _button = [[ + { + "text": f"بررسی مغایرت", + "callback_data": f"subject_unities:qq:{qanon_id}", + } + ]] + # end_button + if _idx == len(groups_data): + m_button = _button + end_button + else: + m_button = _button + + chunks_data.append([m_text, m_button]) + + print(f'structed_form_subject_unity') + return chunks_data, groups_data async def form_rule_making( self, _input, header="گزاره های حقوقی زیر استخراج شد:\n\n", footer=None @@ -591,6 +677,35 @@ class Formatter: return ["هیچ ماده مرتبطی یافت نشد!"] + async def form2_ss_rules(self, _input: List[SemanticSearchP2P], header): + + if len(_input) > 1: + chunks = [] + main_id = 0 + block = [header] + # -------- 1. group by qanon_id / qanon_title + groups_data = defaultdict(set) + for item in _input: + title = item.db_rule.qanon_title + groups_data[title].add(item) + + for _idx, (qanon_title, sections_data) in enumerate(groups_data.items(), start=1): + main_id += 1 + block += [f"{self.number(main_id)} در قانون {self.bold(qanon_title)}:"] + for _idx2, sec_data in enumerate(sections_data, start=1): + block +=[f"تشابه با گزاره های حقوقی: {self.bold(sec_data.db_rule.section_full_path)} {self.__make_link_qs(sec_data.db_rule.section_id)} یافت شد."] + + if len("\n".join(block)) > self.max_len: + chunks.append("\n".join(block)) + block=[header] + + if block and "".join(block) != header: + chunks.append("\n".join(block)) + + return chunks + + return ["هیچ ماده مرتبطی یافت نشد!"] + async def form_conflict_detection( self, _input: RuleRelation, header="نتیجه تشخیص مغایرت :\n" ): @@ -601,7 +716,7 @@ class Formatter: current += f"به صورت خلاصه {_input.conflict_detection.has_confict}\n" current += f"توضیحات : {_input.conflict_detection.explanation_of_conflict}\n" - return current + return [current] async def form_conflict_type_detection( self, _input: RuleRelation, header="نتیجه تشخیص نوع مغایرت :\n" @@ -613,7 +728,7 @@ class Formatter: current += f"به صورت خلاصه {_input.conflict_type_detection.conflict_type}\n" current += f"توضیحات : {_input.conflict_type_detection.explanation_of_type}\n" - return current + return [current] async def form_relation_identification( self, _input: RuleRelation, header="نتیجه رابطه مغایرت :\n" @@ -625,25 +740,23 @@ class Formatter: current += f"به صورت خلاصه {_input.relation_identification.relation_type}\n" current += f"توضیحات : {_input.relation_identification.reasoning}\n" - return current + return [current] async def form_evaluation( self, _input: Evaluation, header="نتیجه نهایی بررسی مغایرت :\n" ): - current = header + current = [header] # ساخت لینک # _link = self.__make_link_qs(src=_input.db_rule.section_id) - current += f"1. آیا ارزیابی وحدت موضوع صحیح است؟ {_input.is_subject_unity_assessment_correct}\n" - current += f"2. آیا ارزیابی تشخیص نوع درست است ؟ {_input.is_conflict_detection_correct}\n" - current += f"3. آیا ارزیابی نوع درست است ؟ {_input.is_conflict_type_detection_correct}\n" - current += ( - f"4. رابطه مغایرت چطور؟ {_input.is_relation_type_detection_correct}\n" - ) - current += f"5. نوع رابطه ؟ {_input.valid_relation_type}\n" - current += f"6.توضیح بیشتر: {_input.comments}\n" + current += [f"1. آیا ارزیابی وحدت موضوع صحیح است؟ {_input.is_subject_unity_assessment_correct}"] + current += [f"2. آیا ارزیابی تشخیص نوع درست است ؟ {_input.is_conflict_detection_correct}"] + current += [f"3. آیا ارزیابی نوع درست است ؟ {_input.is_conflict_type_detection_correct}"] + current += [f"4. رابطه مغایرت چطور؟ {_input.is_relation_type_detection_correct}"] + current += [f"5. نوع رابطه ؟ {_input.valid_relation_type}"] + current += [f"6.توضیح بیشتر: {_input.comments}"] - return current + return "\n".join(current) async def from_law_writing_policy( self, _input_dict: Dict, header: str diff --git a/core/operation.py b/core/operation.py index 61b6ec4..d3ef91b 100644 --- a/core/operation.py +++ b/core/operation.py @@ -30,7 +30,7 @@ class Operation: url="new/semantic_search", ) - return BMNewSemanticSearchOutput.parse_obj(result) + return BMNewSemanticSearchOutput.model_validate(result) async def stream_search_in_law( self, query: str, limit: int, rerank_model: str, embed_model: str @@ -113,7 +113,7 @@ class Operation: url="/single/semantic_search/run_chat", ) print(f"chat_in_law {result}") - return ChatLaw.parse_obj(result) + return ChatLaw.model_validate(result) async def title_repeated( self, diff --git a/core/static.py b/core/static.py index f765b70..7d9cd9e 100755 --- a/core/static.py +++ b/core/static.py @@ -1,19 +1,33 @@ -################# modularity -### import from external-package -# from fastapi import FastAPI, Request, HTTPException -# import requests, logging, asyncio, httpx, os, uuid, traceback, orjson, copy, uvicorn, time, re -# from dotenv import load_dotenv -# from pathlib import Path -# from time import sleep -# from enum import Enum -# from typing import Dict -# from utils.base_model import RuleRelation +################# Static-Ui-Controll +### bottuns +B_PREVIOUS = "⬅️ مرحله قبل" +B_HOME = "🏠 خانه" +B_10MORE = "بارگذاری نتایج بیشتر 10+" +B_MORE_EFFORT = "بررسی عمیق تر" +B_STOP_PROCCESS = "توقف عملیات در حال اجراء" +B_WAIT = "⌛ در حال پردازش درخواست شما هستم..." +B_CHOICE_SUMMARY = "نمایش به صورت خلاصه" +B_CHOICE_FULL= "نمایش به صورت کامل" +### static-text-message/callback +""" +پیام های رزرو شده برای در نظر گرفتن بعنوان CALLBACK و دکمه های کیبورد +""" +MC_JOST_JO = "جستجو" +MC_GOFT_GO = "گفتگو" +MC_HOME = "بازگشت به خانه" +### State-Static-Messages +SM_CHOICE = """لطفا یک مورد را انتخاب کنید:""" #برای انتخاب بین چند دکمه -################################################# -# پارامتر های کلی -################################################# +### Core-Options +## Delay-Time +D_TIME_CHAT = 0.05 +D_TIME_UI_TEMP_CHAT = 0.4 +## limit +REQUEST_FREEZE_LIMIT = 3 +MAX_RAG_LIMIT = 100 +BUSY_TEXT = "⏳ درخواست قبلی شما در حال پردازش هست، لطفا تا اتمام آن منتظر بمانید ⏳" EFFORT = "low" MAX_LIMIT_RAG = 100 # بیشینه تعدادی که rag برمی گرداند STEP_RAG = 10 # مقداری که هر مرحله rag اضافه میکند با فشردن نمایش بیشتر more @@ -24,3 +38,4 @@ QQ_WEB_LINK = ( "https://majles.tavasi.ir/entity/navigation/view/qasection/" # آدرس صفحه qq ها ) REF_TEXT = "«منبع»" # برای نمایش منبع + diff --git a/inProcees_md.txt b/inProcees_md.txt new file mode 100644 index 0000000..f9e56dd --- /dev/null +++ b/inProcees_md.txt @@ -0,0 +1,271 @@ + +result ==> # Flowchart: Bale-Bot Backend System + +## Main Application Flow + +``` +┌─────────────────────────────────────┐ +│ main.py │ +└──────────┬────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ Lifespan Manager (Startup) │ +│ ┌─────────────────────────────┐ │ +│ │ Load .env variables │ │ +│ │ Validate required vars │ │ +│ │ Initialize ElasticHelper │ │ +│ │ Set webhook URL │ │ +│ │ Initialize UserManager │ │ +│ │ Initialize Formatter │ │ +│ │ Initialize RequestManager │ │ +│ │ Initialize Operation │ │ +│ │ Initialize BaleBotCore │ │ +│ └─────────────────────────────┘ │ +└──────────┬────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ Application Factory │ +│ ┌─────────────────────────────┐ │ +│ │ Create FastAPI app │ │ +│ │ Add CORS middleware │ │ +│ │ Add health routes (/, /ping)│ │ +│ │ Include Bale router │ │ +│ └─────────────────────────────┘ │ +└──────────┬────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ FastAPI App Running │ +└─────────────────────────────────────┘ +``` + +## Core Classes Flow + +### 1. ElasticHelper Class +``` +┌─────────────────────────────────────┐ +│ ElasticHelper │ +├─────────────────────────────────────┤ +│ __init__(es_url, es_pass, es_user) │ +│ ┌─────────────────────────────┐ │ +│ │ Connect to Elasticsearch │ │ +│ │ Retry connection 10 times │ │ +│ └─────────────────────────────┘ │ +│ │ +│ search(**params) │ +│ get_document(index, id) │ +│ exist_document(index, id) │ +│ update_index_doc(is_update, ...) │ +└─────────────────────────────────────┘ +``` + +### 2. Formatter Class +``` +┌─────────────────────────────────────┐ +│ Formatter │ +├─────────────────────────────────────┤ +│ __init__(max_len) │ +│ ┌─────────────────────────────┐ │ +│ │ Initialize formatting tools │ │ +│ │ Number mapping │ │ +│ └─────────────────────────────┘ │ +│ │ +│ __getattr__(name) │ +│ _bold(text) │ +│ _number(value) │ +│ _pretier1(text) │ +│ __make_link_qq(src) │ +│ __make_link_qs(src) │ +│ │ +│ form_search_in_law_rules(...) │ +│ form_search_in_law(...) │ +│ form_law_chat(answer_text) │ +│ form_title_repeated(...) │ +│ form_chat(llm_text, header) │ +│ form_llm_answer_chat(...) │ +│ form_subject_unity(...) │ +│ structed_form_subject_unity(...) │ +│ form_rule_making(...) │ +│ get_asl(_in) │ +│ get_in_form_single(asl, ...) │ +│ form_constitution(input) │ +│ form_constitution_low(...) │ +│ form_ss_rules(...) │ +│ form2_ss_rules(...) │ +│ form_conflict_detection(...) │ +│ form_conflict_type_detection(...) │ +│ form_relation_identification(...) │ +│ form_evaluation(...) │ +│ from_law_writing_policy(...) │ +└─────────────────────────────────────┘ +``` + +### 3. RequestManager Class +``` +┌─────────────────────────────────────┐ +│ RequestManager │ +├─────────────────────────────────────┤ +│ __init__(host_url) │ +│ ┌─────────────────────────────┐ │ +│ │ Set base URL │ │ +│ │ Define task URLs │ │ +│ └─────────────────────────────┘ │ +│ │ +│ get_result(payload, url, ...) │ +│ ┌─────────────────────────────┐ │ +│ │ Async HTTP POST request │ │ +│ │ Handle timeouts/errors │ │ +│ └─────────────────────────────┘ │ +│ │ +│ stream_result(url, payload) │ +│ ┌─────────────────────────────┐ │ +│ │ Stream HTTP POST response │ │ +│ │ Yield JSON lines │ │ +│ └─────────────────────────────┘ │ +└─────────────────────────────────────┘ +``` + +### 4. Operation Class +``` +┌─────────────────────────────────────┐ +│ Operation │ +├─────────────────────────────────────┤ +│ __init__(request_manager) │ +│ │ +│ search_in_law(query, limit, ...) │ +│ stream_search_in_law(...) │ +│ stream_rule_making(...) │ +│ stream_chat_in_law(...) │ +│ stream_rule_semantic_search(...) │ +│ chat_in_law(query, effort, ...) │ +│ title_repeated(qanontitle, ...) │ +│ talk(query) │ +│ conflict_qanon_asasi_low(...) │ +│ conflict_qanon_asasi_steps(...) │ +│ stream_logical_chat_in_law(...) │ +│ conflict_law_writing_policy(...) │ +└─────────────────────────────────────┘ +``` + +### 5. UserManager Class +``` +┌─────────────────────────────────────┐ +│ UserManager │ +├─────────────────────────────────────┤ +│ __init__() │ +│ ┌─────────────────────────────┐ │ +│ │ Load VIP usernames │ │ +│ │ Load temporary data │ │ +│ └─────────────────────────────┘ │ +│ │ +│ get_or_create(update) │ +│ ┌─────────────────────────────┐ │ +│ │ Extract user info │ │ +│ │ Check VIP status │ │ +│ │ Create/update user object │ │ +│ └─────────────────────────────┘ │ +└─────────────────────────────────────┘ +``` + +### 6. BaleBotCore Class +``` +┌─────────────────────────────────────┐ +│ BaleBotCore │ +├─────────────────────────────────────┤ +│ __init__(...) │ +│ ┌─────────────────────────────┐ │ +│ │ Initialize UI handler │ │ +│ │ Setup components │ │ +│ └─────────────────────────────┘ │ +│ │ +│ render_user_state(user) │ +│ ┌─────────────────────────────┐ │ +│ │ Get user state │ │ +│ │ Call appropriate handler │ │ +│ └─────────────────────────────┘ │ +│ │ +│ render_update(update) │ +│ ┌─────────────────────────────┐ │ +│ │ Process incoming updates │ │ +│ │ Route to handlers │ │ +│ └─────────────────────────────┘ │ +│ │ +│ handle_main(user) │ +│ handle_search_in_law(user) │ +│ handle_rule_making(user) │ +│ handle_chat_in_law(user) │ +│ handle_qanon_title_repeat(user) │ +│ handle_talk(user) │ +│ handle_logical_chat_in_law(user) │ +│ handle_conflict_qanon_asasi(user) │ +│ handle_conflict_law_writing_policy(user) │ +│ handle_subject_unities_to_evalution(user) │ +│ handle_summary_conflict_all_qavanin(user) │ +│ handle_full_conflict_all_qavanin(user) │ +│ handle_conflict_general_policy(user) │ +│ handle_advanced_check_conflict(user) │ +│ handle_stream_chat(user) │ +│ handle_beta(user) │ +│ │ +│ save_to_db(user, data) │ +└─────────────────────────────────────┘ +``` + +### 7. BaleBotBase Class +``` +┌─────────────────────────────────────┐ +│ BaleBotBase │ +├─────────────────────────────────────┤ +│ __init__(send_msg_url, ...) │ +│ │ +│ delete_bale_message(chat_id, message_id) │ +│ update_bale_message(user, text, buttons) │ +│ serialize(obj) │ +│ normalize_messages(...) │ +│ send_message_helper(...) │ +│ send_bale_message(...) │ +└─────────────────────────────────────┘ +``` + +### 8. BaleBotUI Class +``` +┌─────────────────────────────────────┐ +│ BaleBotUI │ +├─────────────────────────────────────┤ +│ __init__(delay_time, ...) │ +│ ┌─────────────────────────────┐ │ +│ │ Initialize heartbeat settings │ │ +│ └─────────────────────────────┘ │ +│ │ +│ active(user) │ +│ cancel(user) │ +│ create(user, main_text, waiting_list) │ +│ heartbeat_loop(user, waiting_list) │ +└─────────────────────────────────────┘ +``` + +### 9. StackManager Class +``` +┌─────────────────────────────────────┐ +│ StackManager │ +├─────────────────────────────────────┤ +│ push(output_pure_data, ...) │ +│ last_item(user) │ +│ get_last_item_metadata(user) │ +│ prev_item(user) │ +│ get(user, name) │ +│ cleanup(user) │ +│ set_runtime(user, index, runtime_ms) │ +└─────────────────────────────────────┘ +``` + +## Data Flow Between Components + +``` +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ FastAPI App │───▶│ BaleBotCore │───▶│ RequestManager │ +└─────────────────┘ │ │ │ │ + │ │ │ │ + │ │ │ │ \ No newline at end of file diff --git a/main.py b/main.py index 7b424ef..a49278e 100755 --- a/main.py +++ b/main.py @@ -5,7 +5,7 @@ from core.core import ElasticHelper, Formatter, RequestManager from fastapi.middleware.cors import CORSMiddleware from router.bale.bale import router as bale_router from router.bale.bale import initialize_webhook -from router.bale.bale_handle import BaleBot, UserManager +from router.bale.bale_handle import BaleBotCore, UserManager from core.operation import Operation from dotenv import load_dotenv import os @@ -72,15 +72,17 @@ async def lifespan(app: FastAPI): request_manager=app.state.request_manager, ) # بله بات - bale_bot = BaleBot( + bale_bot = BaleBotCore( operation=app.state.operation, user_manager=app.state.user_manager, es_helper=app.state.es_helper, es_index_name=app.state.es_index_name, - token=app.state.bale_token, formatter= app.state.formatter, back_end_url = BACK_END_URL, request_manager = app.state.request_manager, + send_message_url = f"https://tapi.bale.ai/bot{TOKEN}/sendMessage", + delete_message_url = f"https://tapi.bale.ai/bot{TOKEN}/deleteMessage", + update_message_url = f"https://tapi.bale.ai/bot{TOKEN}/editMessageText", ) app.state.bale_bot = bale_bot print("✅✅✅ Bale-Bot Initialized ✅✅✅") diff --git a/proje_information.md b/proje_information.md index 8df9bc8..295f80f 100644 --- a/proje_information.md +++ b/proje_information.md @@ -9,7 +9,7 @@ Operation (logic layer) │ └── return OperationResult -BaleBot (delivery / interface layer) +BaleBotCore (delivery / interface layer) │ ├── دریافت user ├── صدا زدن Operation @@ -19,4 +19,83 @@ BaleBot (delivery / interface layer) # Logic & Flow Operation → منطق، پردازش، گرفتن دیتا، تصمیم‌گیری -BaleBot → ورودی/خروجی، ارتباط با بله، فرمت پیام، دکمه‌ها، مدیریت state کاربر \ No newline at end of file +BaleBotCore → ورودی/خروجی، ارتباط با بله، فرمت پیام، دکمه‌ها، مدیریت state کاربر + + +# FlowChart +/ +└── main.py +│ └── fast-api app +├── core +│ └── core.py +│ ├── class Formatter : فرمت دهی و مدیریت استایل ها و UI +│ ├── class RequestManager : درخواست ها و داده ها را از بک اند میگیرد در دو نوع async (chunk-chunk) و ساده +│ └── class ElasticHelper : مدیریت و ارتباط با الاستیک سرچ +├── router +│ └── bale +│ ├── bale.py +│ │ ├── func webhook : دریافت داده از بله +│ │ └── func initialize_webhook : شناساندن ربات به بله +│ ├── bale_handle.py +│ │ ├── class BaleBotBase : برای مدیریت پیام ها و دکمه ها و 3 url send, update, delet ارتباط با سرور بله +│ │ ├── class BaleBotUI : مدیریت پیام های موقتی +│ │ ├── class StackManager : مدیریت داده های کاربر در پشته یا stack +│ │ └── class BaleBotCore : ساختار ربات و انجام state ها +│ │ ├── func render_update : مسئول گرفتن داده از بله به دو صورت BaleStartMessage یا BaleCallbackQuery +│ │ ├── func render_user_state : وظیفه مدیریت و پاس دادن هر ورودی به بر اساس state به تابع موردنظر +│ │ ├── func handle_previous_message : دسترسی به پیام مرحله قبل (بزودی باید با stack هندل شود) +│ │ ├── func handle_main : صفحه اول - حذف کننده داده ها و ریست داده ها +│ │ ├── func handle_search_in_law : +│ │ ├── func handle_search_in_law_rules +│ │ ├── func handle_rule_making +│ │ ├── func handle_chat_in_law +│ │ ├── func handle_qanon_title_repeat +│ │ ├── func handle_logical_chat_in_law +│ │ ├── func handle_conflict_qanon_asasi +│ │ ├── func handle_conflict_law_writing_policy +│ │ ├── func handle_subject_unities_to_evalution +│ │ ├── func show_stack_data +│ │ ├── func handle_summary_conflict_all_qavanin +│ │ ├── func save_to_db +│ │ └── + + + + + + + + + + + + + + + + +# input-flow + + User-Input + ↓ +app → initialize_webhook → Bale-Server + ↓ + webhook + ↓ + BaleBotCore + └→ BaleBotBase + └→ BaleBotUI + └→ StackManager + └→ UserManager + └→ Operation + + + + + + + ↑ + ↓ + ← + → + diff --git a/requierments.txt b/requierments.txt index 6a52e29..d089e7a 100755 --- a/requierments.txt +++ b/requierments.txt @@ -1,4 +1,3 @@ elasticsearch==8.13.2 nltk -pydantic -fast-api \ No newline at end of file +pydantic \ No newline at end of file diff --git a/router/bale/bale.py b/router/bale/bale.py index 0a34491..6769884 100755 --- a/router/bale/bale.py +++ b/router/bale/bale.py @@ -1,7 +1,7 @@ # router.bale_bot.py from fastapi import Depends, APIRouter, Request import requests, traceback -from router.bale.bale_handle import BaleBot, BaleUpdate +from router.bale.bale_handle import BaleBotCore, BaleUpdate from dependencies import _get_bale_token, _get_bale_bot @@ -20,7 +20,7 @@ chat_id async def webhook( request: Request, token: str = Depends(_get_bale_token), - bale_bot: BaleBot = Depends(_get_bale_bot), + bale_bot: BaleBotCore = Depends(_get_bale_bot), ): raw = await request.json() # print(' webhook request ', raw) diff --git a/router/bale/bale_buttons.py b/router/bale/bale_buttons.py index 304db48..52491ad 100755 --- a/router/bale/bale_buttons.py +++ b/router/bale/bale_buttons.py @@ -1,11 +1,17 @@ -BACK_BUTTON = {"text": "⬅️ مرحله قبل", "callback_data": "workflow_back"} -HOME_BUTTON = {"text": "🏠 خانه", "callback_data": "main"} -MORE_LIMIT_BUTTON = {"text": "بارگذاری نتایج بیشتر 10+", "callback_data": "more_limit"} -# MORE_EFFORT_BUTTON = {"text": "🧠 بررسی عمیق تر", "callback_data": "more_effort"} -MORE_EFFORT_BUTTON = {"text": "بررسی عمیق تر", "callback_data": "more_effort"} +from core.static import * + +PREVIOUS_BUTTON = {"text": B_PREVIOUS, "callback_data": "previous_message"} +HOME_BUTTON = {"text": B_HOME, "callback_data": "main"} +MORE_LIMIT_BUTTON = {"text": B_10MORE, "callback_data": "more_limit"} +MORE_EFFORT_BUTTON = {"text": B_MORE_EFFORT, "callback_data": "more_effort"} +STOP_PROCCESS = {"text":B_STOP_PROCCESS, "callback_data":"main"} + + + + BUTTON_TEXT_TO_CALLBACK_LIST = [ - {"text": "جستجو"}, - {"text": "گفتگو"}, - {"text": "بازگشت به خانه"}, + {"text": MC_JOST_JO}, + {"text": MC_GOFT_GO}, + {"text": MC_HOME}, ] diff --git a/router/bale/bale_handle.py b/router/bale/bale_handle.py index 13b443b..c5a342d 100755 --- a/router/bale/bale_handle.py +++ b/router/bale/bale_handle.py @@ -15,30 +15,20 @@ from core.static import * from router.bale.config import * -class BaleBotGeneral: +class BaleBotBase: """ کلاس اصلی ارتباط با Bale و متد های اصلی ارتباط """ def __init__( self, - token: str, - send_message_url: Optional[str] = None, - delete_message_url: Optional[str] = None, - update_message_url: Optional[str] = None, + send_message_url: str, + delete_message_url: str, + update_message_url: str, ): - self.token = token - - self.send_message_url = ( - send_message_url or f"https://tapi.bale.ai/bot{self.token}/sendMessage" - ) - self.delete_message_url = ( - delete_message_url or f"https://tapi.bale.ai/bot{self.token}/deleteMessage" - ) - self.update_message_url = ( - update_message_url - or f"https://tapi.bale.ai/bot{self.token}/editMessageText" - ) + self.send_message_url = send_message_url + self.delete_message_url = delete_message_url + self.update_message_url = update_message_url async def delete_bale_message(self, chat_id, message_id): payload = { @@ -51,10 +41,11 @@ class BaleBotGeneral: print(f"Delete Message Status code:", r.status_code) except Exception: + print(f"Delete Message Status code:", r.status_code, f"r {r}") print("ERROR in Delete Message Status code:") traceback.print_exc() - async def update_message_to_bale( + async def update_bale_message( self, user: BaleUser, text: str = None, @@ -79,38 +70,82 @@ class BaleBotGeneral: try: r = requests.post(self.update_message_url, json=payload) - if not r.ok: - print("ERROR in Update Message Bale API Error:", r.status_code, r.text) - print(f"Update Message Status code:", r.status_code) + except Exception: + print("ERROR in Update Message Bale API Error:", r.status_code, r) print("ERROR in Update Message Status code:") traceback.print_exc() + async def serialize(self, obj): + if isinstance(obj, BaseModel): + return obj.model_dump() + elif isinstance(obj, list): + return [await self.serialize(i) for i in obj] + elif isinstance(obj, dict): + return {k: await self.serialize(v) for k, v in obj.items()} + else: + return obj + + async def normalize_messages( + self, + text: Optional[str] = None, + chunked_text: Optional[List[str]] = None, + structured_text: Optional[List] = None, + end_buttons: List[BaleButton] = [], + ) -> List[BaleMessage]: + + messages: List[BaleMessage] = [] + + if structured_text: + for text_, buttons in structured_text: + messages.append(BaleMessage(text=text_, buttons=buttons)) + + elif chunked_text: + for chunk in chunked_text: + messages.append(BaleMessage(text=chunk)) + + elif text: + messages.append(BaleMessage(text=text)) + + # attach end buttons to last message + if messages and end_buttons: + messages[-1].buttons.extend(end_buttons) + + return messages + async def send_message_helper( self, user: BaleUser, update_message: bool = False, - text: str = None, - chunked_text: List[str] = None, - structed_output: List = None, - end_buttons: List = [], + text: Optional[str] = None, + chunked_text: Optional[List[str]] = None, + structured_text: Optional[List] = None, + end_buttons: List[List[BaleButton]] = [], reply_markup: List = BUTTON_TEXT_TO_CALLBACK_LIST, is_save_active_message_id=False, ): + _i = 0 if text: _i += 1 if chunked_text: _i += 1 - if structed_output: + if structured_text: _i += 1 if _i != 1: raise ValueError( - "In send_message_helper Only Send One Of {text, chunked_text, structed_output}" + "In send_message_helper Only Send One Of {text, chunked_text, structured_text}" ) + messages: List[BaleMessage] = await self.normalize_messages( + text=text, + chunked_text=chunked_text, + structured_text=structured_text, + end_buttons=end_buttons, + ) + if update_message == True: if user.active_message_id == 0: await self.send_message_helper( @@ -119,7 +154,7 @@ class BaleBotGeneral: text=text, end_buttons=end_buttons, chunked_text=chunked_text, - structed_output=structed_output, + structured_text=structured_text, reply_markup=reply_markup, is_save_active_message_id=is_save_active_message_id, ) @@ -128,10 +163,10 @@ class BaleBotGeneral: new_text = text if chunked_text: new_text = chunked_text[-1] - if structed_output: - new_text = structed_output[-1][0] + if structured_text: + new_text = structured_text[-1][0] - await self.update_message_to_bale( + await self.update_bale_message( user=user, text=new_text, buttons=end_buttons, @@ -139,50 +174,23 @@ class BaleBotGeneral: ) else: - if structed_output: - for i, item in enumerate(structed_output, start=1): - - if i == len(structed_output) and len(end_buttons) > 0: # is_last - item[1] += end_buttons - - await self.send_message_to_bale( - user=user, - text=item[0], - buttons=item[1], - reply_markup=reply_markup, - is_save_active_message_id=is_save_active_message_id, - ) - - if chunked_text: - _buttons = [] - for i, item in enumerate(chunked_text, start=1): - - if i == len(chunked_text) and len(end_buttons) > 0: # is_last - _buttons = end_buttons - - await self.send_message_to_bale( - user=user, - text=item, - buttons=_buttons, - reply_markup=reply_markup, - is_save_active_message_id=is_save_active_message_id, - ) - await asyncio.sleep(0.05) - - if text: - await self.send_message_to_bale( + for i, msg in enumerate(messages): + await self.send_bale_message( user=user, - text=text, - buttons=end_buttons, + text=msg.text, + buttons=msg.buttons, reply_markup=reply_markup, is_save_active_message_id=is_save_active_message_id, ) - async def send_message_to_bale( + if i < len(messages) - 1: + await asyncio.sleep(D_TIME_CHAT) + + async def send_bale_message( self, user: BaleUser, text: str, - buttons: List = [], + buttons: List[List[BaleButton]] | List[List[Dict]] = [], reply_markup: List = BUTTON_TEXT_TO_CALLBACK_LIST, is_save_active_message_id=False, ): @@ -198,26 +206,218 @@ class BaleBotGeneral: } if buttons: - payload["reply_markup"]["inline_keyboard"] = buttons + payload["reply_markup"]["inline_keyboard"] = await self.serialize(buttons) try: + r = requests.post(self.send_message_url, json=payload) - - if not r.ok: - print("ERROR in Send Message Bale API Error:", r.status_code, r.text) - - r_ = r.json() print(f"Send Message Status code:", r.status_code) + r = r.json() if is_save_active_message_id: - user.active_message_id = int(r_["result"]["message_id"]) + user.active_message_id = int(r["result"]["message_id"]) except Exception: + # print("ERROR in Send Message Bale API Error:", r.status_code, r) print("ERROR in Send Message Status code:") traceback.print_exc() -class BaleBot(BaleBotGeneral): +class BaleBotUI(BaleBotBase): + """ + مسئول نمایش «در حال تحلیل…» و پاک‌کردن پیام اصلی + در زمان لغو یا پایان کار است. + برای سرگرم کردن کاربر + یک روند کاملا مجزا برای نشان دادن پیام و حذف در انتها + """ + + def __init__( + self, + send_message_url: str, + delete_message_url: str, + update_message_url: str, + delay_time: float, + ): + super().__init__( + send_message_url=send_message_url, + delete_message_url=delete_message_url, + update_message_url=update_message_url, + ) + """ + :param bot: نمونهٔ BaleBotCore که متدهای API را در اختیار دارد + :param user: کاربری که پیامش در حال پردازش است + """ + self.delay_time = delay_time + self.done = False + self._heartbeat_task: Dict[str, asyncio.Task] = {} + + # ------------------------------------------------------------------ + # توابعی که در والد BaleBotBase هستند + # ------------------------------------------------------------------ + async def delete_bale_message(self, *args, **kwargs): + await super().delete_bale_message(*args, **kwargs) + + async def send_message_helper(self, *args, **kwargs): + await super().send_message_helper(*args, **kwargs) + + # ------------------------------------------------------------------ + # توابع خود کلاس + # ------------------------------------------------------------------ + async def active( + self, + user: BaleUser, + ): + await self.send_message_helper( + user=user, + text=self.main_text[0], + update_message=False, + is_save_active_message_id=True, + ) + + # ------------------------------------------------------------------ + # متدهای عمومی + # ------------------------------------------------------------------ + def cancel( + self, + user: BaleUser, + ): + """ + متوقف کردن حلقهٔ heartbeat و پاک‌کردن پیام اصلی + """ + self.done = True + _user_task = self._heartbeat_task.get(user.uc_id, None) + if _user_task: + self._heartbeat_task[user.uc_id].cancel() # اگر هنوز در حال اجرا باشد + # پاک‌کردن پیام اصلی + asyncio.create_task( + self.delete_bale_message( + chat_id=user.chat_id, + message_id=user.active_message_id, + ) + ) + self.main_text = ["در حال تحلیل"] + + def create( + self, + user: BaleUser, + main_text: str | list = ["در حال تحلیل"], + waiting_list: list = [], + ): + """ + شروع حلقهٔ heartbeat + """ + if isinstance(main_text, str): + self.main_text = [main_text] + elif isinstance(main_text, list): + self.main_text = main_text + + print(f"create create") + self._heartbeat_task[user.uc_id] = asyncio.create_task( + self.heartbeat_loop(user=user, waiting_list=waiting_list) + ) + + # ------------------------------------------------------------------ + # حلقهٔ heartbeat + # ------------------------------------------------------------------ + async def heartbeat_loop(self, user: BaleUser, waiting_list=[]): + """ + هر 0.2 ثانیه متن «در حال تحلیل…» را به‌روزرسانی می‌کند. + """ + await self.active(user=user) + + if len(waiting_list) == 0: + waiting_list = [".", "..", "...", "...."] + + while not self.done: + text = random.choice(self.main_text) + + for j in waiting_list: + # ویرایش پیام + _text = f"{j} {text}" + await self.send_message_helper( + user=user, + text=_text, + update_message=True, + is_save_active_message_id=True, + ) + await asyncio.sleep(self.delay_time) + + # صبر 5 ثانیه + # await asyncio.sleep(0.2) + + +class StackManager: + + # --- push مرحله جدید + def push( + self, + output_pure_data, + user: BaleUser, + step_name: str, + metadata: Dict, + output_formed_data: Union[PreviousMessage, None] = None, + input_data=None, + runtime_ms: int = -1, + ): + """ + وارد کردن داده داخل پشته + رفتار: + اگر input_data برابر با None بود پس داده از مرحله قبل آمده است پس index مرحله قبل را در این فیلد وارد میکنیم + + """ + try: + if not input_data: + input_data = len(user.stack) + + user.stack.append( + StackItem( + index=len(user.stack), + input_data=input_data, + output_pure_data=output_pure_data, + output_formed_data=output_formed_data, + step_name=step_name, + metadata=metadata, + runtime_ms=runtime_ms, + ) + ) + except Exception as e: + print(f"StackManager>push {traceback.print_exc()}") + + # --- آخرین مرحله + def last_item(self, user: BaleUser) -> StackItem | None: + """گرفتن آخرین داده داخل پشته""" + return user.stack[-1] if user.stack else None + + def get_last_item_metadata(self, user: BaleUser) -> Dict | None: + """گرفتن آخرین داده داخل پشته""" + return dict(user.stack[-1].metadata) if user.stack else None + + # --- مرحله قبل + def prev_item(self, user: BaleUser) -> StackItem | None: + """گرفتن داده مرحله قبل از پشته""" + return user.stack[-2] if len(user.stack) >= 2 else None + + # --- گرفتن مرحله با نام + def get(self, user: BaleUser, name: str) -> StackItem | None: + """گرفتن داده بر اساس نام داده از پشته""" + for item in reversed(user.stack): + if item.step_name == name: + return item + return None + + # --- حذف مراحل اضافی (cleanup) + def cleanup(self, user: BaleUser): + """حذف پشته""" + user.stack = [] + + # --- ثبت runtime + def set_runtime(self, user: BaleUser, index: int, runtime_ms: float): + """وارد کردن runtime""" + if 0 <= index < len(user.stack): + user.stack[index].runtime_ms = runtime_ms + + +class BaleBotCore(BaleBotBase): """ input → set_state → render_user_state """ @@ -227,78 +427,101 @@ class BaleBot(BaleBotGeneral): user_manager: UserManager, operation: Operation, es_index_name: str, - token: str, es_helper: ElasticHelper, formatter: Formatter, back_end_url: str, request_manager: RequestManager, + send_message_url: str, + delete_message_url: str, + update_message_url: str, ): super().__init__( - token=token, + send_message_url=send_message_url, + delete_message_url=delete_message_url, + update_message_url=update_message_url, ) - self.ui_handler = UIState( - delay_time=0.4, - token=token, + self.ui_handler = BaleBotUI( + delay_time=D_TIME_UI_TEMP_CHAT, + send_message_url=send_message_url, + delete_message_url=delete_message_url, + update_message_url=update_message_url, ) - self.freeze_limit = 3 + self.stack: List[StackItem] = [] + self.freeze_limit = REQUEST_FREEZE_LIMIT self.operator = operation self.formatter = formatter self.request_manager = request_manager self.es_helper = es_helper - self.token = token self.es_index_name = es_index_name self.user_manager = user_manager - self.max_limit = 100 + self.max_limit = MAX_RAG_LIMIT self.back_end_url = back_end_url - """ - deleteMessage - message_id - chat_id - """ # self.user_state = self.make_state() + @property + def stack_manager(self) -> StackManager: + return StackManager() + # ------------------------------------------------------------------ - # توابعی که در والد BaleBotGeneral هستند + # توابعی که در والد BaleBotBase هستند # ------------------------------------------------------------------ async def delete_bale_message(self, **kwargs): await super().delete_bale_message(**kwargs) - async def update_message_to_bale(self, *args, **kwargs): - await super().update_message_to_bale(*args, **kwargs) + async def update_bale_message(self, *args, **kwargs): + await super().update_bale_message(*args, **kwargs) - async def send_message_helper(self, *args, **kwargs): - await super().send_message_helper(*args, **kwargs) - - async def send_message_to_bale(self, *args, **kwargs): - await super().send_message_to_bale(*args, **kwargs) + async def send_message_helper( + self, + user: BaleUser, + update_message: bool = False, + text: str = None, + chunked_text: List[str] = None, + structured_text: List = None, + end_buttons: List = [], + reply_markup: List = BUTTON_TEXT_TO_CALLBACK_LIST, + is_save_active_message_id: bool = False, + ): + await super().send_message_helper( + user=user, + update_message=update_message, + text=text, + chunked_text=chunked_text, + structured_text=structured_text, + end_buttons=end_buttons, + reply_markup=reply_markup, + is_save_active_message_id=is_save_active_message_id, + ) + async def send_bale_message(self, *args, **kwargs): + await super().send_bale_message(*args, **kwargs) # ------------------------------------------------------------------ # توابع همین کلاس # ------------------------------------------------------------------ - async def render_user_state(self, user: BaleUser): """Bus سیستم""" try: - print(f"user.input_query.render_user_state >{user.input_query}<") - print(f"user.state >{user.state_detail.state}<") - if user.input_query == "": + _state = user.state_detail.state if user.state_detail else None + _input_query = user.input_query if user.input_query else "" + print(f"user.input_query.render_user_state >{_input_query}<") + print(f"user.state >{_state}<") + + if user.input_query == "" and user.state_detail.message != "": + print(f"") + await self.send_message_helper( user=user, text=user.state_detail.message, end_buttons=user.state_detail.end_buttons, ) return {"ok": True} - # if user.input_query != "" and not run_internal and - # and user.last_input_query == "" - - # if user.last_input_query != "" and user.input_query != "" and "subject_unities": - # await self.handle_advanced_check_conflict(user) if user.state_detail.handler: user.is_processing_lock = True + # print(f'fdffffffffff {user.state_detail.handler}') handler = getattr(self, user.state_detail.handler) await handler(user) @@ -319,7 +542,7 @@ class BaleBot(BaleBotGeneral): # 2️⃣ ساخت یا بازیابی user user = self.user_manager.get_or_create(update) - print(f"render_update user -> {user.uc_id} user.is_processing_lock {user.is_processing_lock }") + # print(f"render_update user -> {user.uc_id} user.is_processing_lock {user.is_processing_lock }") if user.is_processing_lock == False: if update.message: user.message_limit = 0 @@ -342,7 +565,7 @@ class BaleBot(BaleBotGeneral): # --- WorkFlow Logic by State await self.render_user_state(user) return {"ok": True} - + if update.callback_query: """ اینجا فقط باید مرحله و state عوض شود و داده ای وارد نمیشود!!! @@ -353,14 +576,66 @@ class BaleBot(BaleBotGeneral): user.is_call_back_query = True user.call_back_query = user.update.callback_query.data # user.update.callback_query.data - run_internal = None + call_back_query = user.call_back_query - if user.call_back_query == "main": + if call_back_query == "main": await self.handle_main(user) return {"ok": True} + elif call_back_query.startswith("show_stack_data:"): + stack_data_name = call_back_query.split(":")[1] + await self.show_stack_data( + user=user, _data_key_name=stack_data_name + ) + return {"ok": True} + + elif call_back_query.startswith("subject_unities:"): + _, _type, _id = call_back_query.split(":") + if _type == "qq": + map_qq_qs = self.stack_manager.get_last_item_metadata(user)[ + "map_qq_qs" + ] + qs_data = map_qq_qs[_id] + print(f"qs_data {qs_data}") + if len(qs_data) == 1: + print("A" * 15) + user.call_back_query = f"subject_unities:qs:{list(qs_data)[0].db_rule.section_id}" + user.state_detail = STATE_CONFIG[ + "subject_unities_to_evalution" + ] + + else: + print("B") + _button = [] + for i, item in enumerate(qs_data, start=1): + _button.append( + [ + { + "text": i, + "callback_data": f"subject_unities:qs:{item.db_rule.section_id}", + } + ] + ) + + _t1 = f"برای ادامه یک مورد را از این قانون انتخاب کنید{item.db_rule.qanon_title} انتخاب کنید" + + print("B" * 10) + print(f"_t1 {_t1}") + print("B" * 10) + + await self.send_message_helper( + user=user, + text=_t1, + end_buttons=_button, + ) + print("C" * 10) + return {"ok": True} + + elif _type == "qs": + user.state_detail = STATE_CONFIG["subject_unities_to_evalution"] + # Dynamic Change Options - if user.call_back_query == "more_limit": + elif user.call_back_query == "more_limit": user.limit += 10 user.input_query = user.last_input_query if user.state_detail == STATE_CONFIG["logical_chat_in_law"]: @@ -376,12 +651,9 @@ class BaleBot(BaleBotGeneral): user.input_query = user.last_input_query user.state_detail = STATE_CONFIG["logical_chat_in_law"] - elif user.call_back_query.startswith("subject_unities:"): - run_internal = "subject_unities" - # subject_unities:qq:qq_983214 - # Dynamic Change State else: + print(f"Dynamic Change State -> {user.state_detail}") user.state_detail = STATE_CONFIG[user.call_back_query] await self.render_user_state(user) @@ -390,21 +662,62 @@ class BaleBot(BaleBotGeneral): if user.call_back_query == "main": await self.handle_main(user) return {"ok": True} - + # beta-mode if user.message_limit < self.freeze_limit: await self.send_message_helper( - user=user, - text=BUSY_TEXT, - end_buttons=[[{"text":"توقف عملیات در حال اجراء", "callback_data":"main"}]] - ) + user=user, text=BUSY_TEXT, end_buttons=[[STOP_PROCCESS]] + ) user.message_limit += 1 return {"ok": True} - async def talk(self, user: BaleUser): + async def handle_previous_message(self, user: BaleUser): + """ + دسترسی به پیام مرحله قبل + """ + print("=============================== handle_previous_message") + try: + # if user.call_back_query == "previous_message": + if user.previous_message: - pass + _type = user.previous_message.type + + if _type == "text": + await self.send_message_helper( + user=user, + text=user.previous_message.message, + end_buttons=user.previous_message.buttons, + ) + + elif _type == "structured_text": + await self.send_message_helper( + user=user, + structured_text=user.previous_message.message, + end_buttons=user.previous_message.buttons, + ) + + elif _type == "chunked_text": + await self.send_message_helper( + user=user, + chunked_text=user.previous_message.message, + end_buttons=user.previous_message.buttons, + ) + + else: + await self.handle_main(user) + + else: + await self.handle_main(user) + + except Exception as e: + print("ERROR in handle_chat:", str(traceback.print_exc())) + await self.send_message_helper(user=user, text=ERROR_IN_PROCESS) + + finally: + user.is_processing_lock = False + + return {"ok": True} async def handle_main(self, user: BaleUser): # if user.input_query != "": @@ -434,22 +747,6 @@ class BaleBot(BaleBotGeneral): end_buttons=MAIN_BUTTON, ) - async def choice_button(self, user: BaleUser, choice_buttons): - # handle_conflict_law_writing_policy - # handle_conflict_qanon_asasi - # handle_conflict_general_policy - # handle_conflict_all_qavanin - result = await self.send_message_helper( - user=user, - text="یک مورد را انتخاب کنید:", - end_buttons=choice_buttons, - ) - - """ - -> Response from render message -> return - """ - return None - async def handle_search_in_law(self, user: BaleUser): user.is_processing_lock = True @@ -674,8 +971,8 @@ class BaleBot(BaleBotGeneral): f_data = data[status] f_metadata = metadata res_ = await self.formatter.form_rule_making( - _input=data[status], - footer=f'تعداد توکن های مصرف شده {_max_token}', + _input=data[status], + footer=f"تعداد توکن های مصرف شده {_max_token}", ) if user.effort != "medium": @@ -694,7 +991,6 @@ class BaleBot(BaleBotGeneral): _result2 = await self.formatter.form_chat( header=" *reasoning* :\n", llm_text=metadata["llm_reason"], - ) await self.send_message_helper( @@ -706,7 +1002,7 @@ class BaleBot(BaleBotGeneral): _max_token = data["max_token"] if _max_token: self.ui_handler.main_text = [ - f'تعداد توکن های مصرف شده {_max_token}', + f"تعداد توکن های مصرف شده {_max_token}", ] except Exception as e: @@ -719,13 +1015,9 @@ class BaleBot(BaleBotGeneral): "state": "rule_making", "result": f_data, "llm_reason": metadata["llm_reason"], - "metadata": { - "llm_token":_max_token - }, + "metadata": {"llm_token": _max_token}, } - print( - f'_data {_data}' - ) + print(f"_data {_data}") _data["metadata"].update(f_metadata) await self.save_to_db(user, data=_data) @@ -739,7 +1031,9 @@ class BaleBot(BaleBotGeneral): async def handle_chat_in_law(self, user: BaleUser): user.is_processing_lock = True - self.ui_handler.create(user=user, main_text=[ + self.ui_handler.create( + user=user, + main_text=[ "در حال تحلیل", "در حال طبقه بندی", "در حال تفکر", @@ -858,7 +1152,7 @@ class BaleBot(BaleBotGeneral): async def handle_logical_chat_in_law(self, user: BaleUser): user.is_processing_lock = True - + self.ui_handler.create(user=user, main_text=["در حال استخراج اجزاء"]) f_data = "" @@ -933,7 +1227,6 @@ class BaleBot(BaleBotGeneral): # _data['metadata'].update(_metadata) await self.save_to_db(user=user, data=_data) - return {"ok": True} async def handle_conflict_qanon_asasi(self, user: BaleUser): @@ -1039,7 +1332,9 @@ class BaleBot(BaleBotGeneral): async def handle_conflict_law_writing_policy(self, user: BaleUser): user.is_processing_lock = True _buttons = [[HOME_BUTTON]] - self.ui_handler.create(user=user, main_text=[ + self.ui_handler.create( + user=user, + main_text=[ "در حال تحلیل", "در حال طبقه بندی", "در حال تفکر", @@ -1074,8 +1369,300 @@ class BaleBot(BaleBotGeneral): return {"ok": True} - async def handle_conflict_all_qavanin(self, user: BaleUser): + async def handle_subject_unities_to_evalution(self, user: BaleUser): user.is_processing_lock = True + _buttons = [[HOME_BUTTON, PREVIOUS_BUTTON]] + _b = [] + try: + qs_id = user.call_back_query.split(":")[-1] # subject_unities:qs:qs2794301 + print(f"/************* handle_subject_unities_to_evalution qs_id {qs_id}") + map_qq_qs = self.stack_manager.get(user, name="subject_unities").metadata[ + "map_qq_qs" + ] + + qs_data = None + for k, v in map_qq_qs.items(): + for item in v: + if item.db_rule.section_id == qs_id: + qs_data = item + break + + qs_data = RuleRelation.model_validate(qs_data) + self.ui_handler.create( + user=user, + main_text=[ + "در حال بررسی", + "تفکر", + "جمع بندی", + ], + ) + + print(f"Send 2 /conflict/unity_eval ") + async for step_data in self.request_manager.stream_result( + payload={ + "rule_relation": qs_data.model_dump(), + "effort": user.effort, + "mode_type": "bale", + }, + url="/conflict/unity_eval", + ): + + step = step_data.get("step") + data = step_data.get("data") + metadata = step_data.get("metadata") + # is_last = step_data.get("is_last") + print(f"==== handle_advanced_check_conflict ====") + print(f"step {step}") + # print(f"data {data}") + print("==" * 20) + if step == "ConflictDetection": + print(f"*********************Step2 {user.effort}") + _header = "نتیجه تشخیص مغایرت" + self.ui_handler.main_text = [_header] + _data = RuleRelation.model_validate(data) + chunked_text = await self.formatter.form_conflict_detection(_data) + _metadata = {} + _metadata["reasoning_content"] = metadata["reasoning_content"] + _metadata["total_token"] = ( + f"کل توکن های مصرف شده: {metadata['total_token']}" + ) + self.stack_manager.push( + user=user, + output_pure_data=_data, + step_name="ConflictDetection", + metadata=_metadata, + output_formed_data=PreviousMessage( + message=chunked_text, type="chunked_text" + ), + ) + _b.append( + BaleButton( + text="تشخیص مغایرت", + callback_data="show_stack_data:ConflictDetection", + ) + ) + + elif step == "ConflictTypeDetection": + print(f"*********************Step3") + _header = "نتیجه تشخیص نوع مغایرت" + self.ui_handler.main_text = [_header] + _data = RuleRelation.model_validate(data) + chunked_text = await self.formatter.form_conflict_type_detection( + _data + ) + _metadata = {} + _metadata["reasoning_content"] = metadata["reasoning_content"] + _metadata["total_token"] = ( + f"کل توکن های مصرف شده: {metadata['total_token']}" + ) + + self.stack_manager.push( + user=user, + output_pure_data=_data, + step_name="ConflictTypeDetection", + metadata=_metadata, + output_formed_data=PreviousMessage( + message=chunked_text, type="chunked_text" + ), + ) + _b.append( + BaleButton( + text="تشخیص نوع مغایرت", + callback_data="show_stack_data:ConflictTypeDetection", + ) + ) + + elif step == "RelationIdentification": + + print(f"*********************Step4") + _header = "ارتباط یابی مغایرتی" + self.ui_handler.main_text = [_header] + _data = RuleRelation.model_validate(data) + chunked_text = await self.formatter.form_relation_identification( + _data, header=_header + ) + _metadata = {} + _metadata["reasoning_content"] = metadata["reasoning_content"] + _metadata["total_token"] = ( + f"کل توکن های مصرف شده: {metadata['total_token']}" + ) + + self.stack_manager.push( + user=user, + output_pure_data=_data, + step_name="RelationIdentification", + metadata=_metadata, + output_formed_data=PreviousMessage( + message=chunked_text, type="chunked_text" + ), + ) + _b.append( + BaleButton( + text="ارتباط یابی مغایرت", + callback_data="show_stack_data:RelationIdentification", + ) + ) + _header = "نهایی سازی" + self.ui_handler.main_text = [_header] + + elif step == "Evaluation": + print(f"*********************Step5") + self.ui_handler.cancel(user) + _data = Evaluation.model_validate(data) + chunked_text = await self.formatter.form_evaluation( + _data, header=_header + ) + _metadata = {} + _metadata["reasoning_content"] = metadata["reasoning_content"] + _metadata["total_token"] = ( + f"کل توکن های مصرف شده: {metadata['total_token']}" + ) + print(f"chunked_text {type(chunked_text)} == {chunked_text}") + self.stack_manager.push( + user=user, + output_pure_data=_data, + step_name="Evaluation", + metadata=_metadata, + output_formed_data=PreviousMessage( + message=chunked_text, buttons=_buttons, type="text" + ), + ) + _buttons.append(_b) + + await self.send_message_helper( + user=user, text=chunked_text, end_buttons=_buttons + ) + else: + print(f"eerror in uknown step --> {step}") + + except Exception as e: + print("ERROR in handle_chat:", str(traceback.print_exc())) + await self.send_message_helper(user=user, text=ERROR_IN_PROCESS) + + finally: + user.last_input_query = user.input_query + user.input_query = "" + user.is_processing_lock = False + + return {"ok": True} + + async def show_stack_data( + self, user: BaleUser, _data_key_name=None, metadata_key=None + ): + user.is_processing_lock = True + _buttons = [[HOME_BUTTON, PREVIOUS_BUTTON]] + + try: + print(f"_data_key_name ={_data_key_name}=") + if _data_key_name: + data = self.stack_manager.get(user, _data_key_name) + else: + data = self.stack_manager.last_item(user) + # data = self.stack_manager.get_last_item_metadata(user) + + if data: + if data.output_formed_data: + if data.output_formed_data.type == "text": + await self.send_message_helper( + user=user, + text=data.output_formed_data.message, + end_buttons=data.output_formed_data.buttons, + ) + + elif data.output_formed_data.type == "chunked_text": + await self.send_message_helper( + user=user, + chunked_text=data.output_formed_data.message, + end_buttons=data.output_formed_data.buttons, + ) + + elif data.output_formed_data.type == "structured_text": + await self.send_message_helper( + user=user, + structured_text=data.output_formed_data.message, + end_buttons=data.output_formed_data.buttons, + ) + + else: + if _data_key_name == "rule_making": + chunked_text = await self.formatter.form_rule_making( + _input=data.output_pure_data + ) + + elif _data_key_name == "semantich_search": + chunked_text = await self.formatter.form2_ss_rules( + data.output_pure_data, header="نتایج جستجوی معنایی " + ) + elif _data_key_name == "ConflictDetection": + chunked_text = await self.formatter.form_conflict_detection( + data.output_pure_data, + ) + elif _data_key_name == "ConflictTypeDetection": + chunked_text = ( + await self.formatter.form_conflict_type_detection( + data.output_pure_data + ) + ) + elif _data_key_name == "RelationIdentification": + chunked_text = ( + await self.formatter.form_relation_identification( + data.output_pure_data, + ) + ) + elif _data_key_name == "Evaluation": + chunked_text = await self.formatter.form_evaluation( + data.output_pure_data + ) + + else: + chunked_text = ["متاسفانه؛ داده مرتبط یافت نشد"] + + print(f"_data_key_name {_data_key_name}") + print( + f"chunked_text {type(chunked_text)} -index-0 {type(chunked_text[0])}" + ) + await self.send_message_helper( + user=user, chunked_text=chunked_text, end_buttons=_buttons + ) + else: + await self.send_message_helper( + user=user, text="متاسفانه؛ داده مرتبط یافت نشد" + ) + + except Exception as e: + print("ERROR in handle_chat:", str(traceback.print_exc())) + await self.send_message_helper(user=user, text=ERROR_IN_PROCESS) + + finally: + user.last_input_query = user.input_query + user.input_query = "" + user.is_processing_lock = False + + return {"ok": True} + + async def handle_summary_conflict_all_qavanin( + self, user: BaleUser, _button: List = [[HOME_BUTTON]] + ): + b1 = { + "text": "اجزاء متن ورودی", + "callback_data": "show_stack_data:rule_making", + } + b2 = { + "text": "اجزاء مرتبط از قانون", + "callback_data": "show_stack_data:semantich_search", + } + _button.append([b1, b2]) + print(f"******************* handle_summary_conflict_all_qavanin") + user.is_processing_lock = True + self.ui_handler.create( + user=user, + main_text=[ + "در حال بررسی", + "تفکر", + "جمع بندی", + ], + ) + try: async for step_data in self.request_manager.stream_result( payload={ @@ -1088,19 +1675,118 @@ class BaleBot(BaleBotGeneral): ): step = step_data.get("step") data = step_data.get("data") - is_first = step_data.get("is_first", False) - is_last = step_data.get("is_last", False) - _button = [[HOME_BUTTON]] - print(f"==== handle_conflict_general_policy ====") - if step == "rule_making": - print(f"rule_making {user.effort}") + _metadata = step_data.get("metadata", {}) + print(f"==== handle_conflict_all_qavanin step {step} ====") + if step == "rule_making": + self.stack_manager.push( + user=user, + input_data=user.input_query, + output_pure_data=data, + step_name="rule_making", + metadata=_metadata, + ) + # List[InputRule] + self.ui_handler.main_text = [ + f"تعداد اجزاء تشخیص داده شده در متن ورودی {len(data)}" + ] + + elif step == "semantich_search": + self.stack_manager.push( + user=user, + output_pure_data=[ + SemanticSearchP2P.model_validate(i) for i in data + ], + step_name="semantich_search", + metadata=_metadata, + ) + # List[SemanticSearchP2P] + self.ui_handler.main_text = [ + f"تعداد مستندات مرتبط یافت شده در قوانین {len(data)}", + "در حال بررسی مغایرت های احتمالی در قوانین", + ] + + elif step == "subject_unities": + self.ui_handler.cancel(user) + _header = "نتایج بررسی وحدت موضوعی :\n" + try: + _data = [RuleRelation.model_validate(i) for i in data] + except: + _data = data + + structured_text, map_qq_qs = ( + await self.formatter.structed_form_subject_unity( + _input=_data, header=_header, end_button=_button + ) + ) + await self.send_message_helper( + user=user, + structured_text=structured_text, + ) + user.last_result = _data + + user.previous_message = PreviousMessage( + message=structured_text, + type="structured_text", + ) + _metadata.update({"map_qq_qs": map_qq_qs}) + self.stack_manager.push( + user=user, + output_pure_data=_data, + output_formed_data=PreviousMessage( + message=structured_text, type="structured_text" + ), + step_name="subject_unities", + metadata=_metadata, + ) + + except Exception as e: + print("ERROR in handle_chat:", str(traceback.print_exc())) + await self.send_message_helper(user=user, text=ERROR_IN_PROCESS) + + finally: + user.last_input_query = user.input_query + user.input_query = "" + user.is_processing_lock = False + self.ui_handler.cancel(user) + + return {"ok": True} + + async def handle_full_conflict_all_qavanin( + self, user: BaleUser, _button: List = [[HOME_BUTTON]] + ): + print(f";;;;;;;;;;;;;;;;;;;; handle_full_conflict_all_qavanin") + user.is_processing_lock = True + self.ui_handler.create( + user=user, + main_text=[ + "در حال بررسی", + "تفکر", + "جمع بندی", + ], + ) + + try: + async for step_data in self.request_manager.stream_result( + payload={ + "section_content": user.input_query, + "effort": user.effort, + "limit": user.limit, + "mode_type": "bale", + }, + url="/conflict/all_qanon/qs_unity", + ): + step = step_data.get("step") + data = step_data.get("data") + metadata = step_data.get("metadata") + print(f"==== handle_conflict_all_qavanin ====") + + if step == "rule_making": chunked_response = await self.formatter.form_rule_making( _input=data ) await self.send_message_helper( user=user, - update_message=False, chunked_text=chunked_response, ) @@ -1111,6 +1797,7 @@ class BaleBot(BaleBotGeneral): await self.send_message_helper(user, chunked_text=response) elif step == "subject_unities": + self.ui_handler.cancel(user) print(f"subject_unities {user.effort}") _header = "نتایج اولیه مغایرت های احتمالی :\n" print(f"data type {type(data)}") @@ -1135,8 +1822,10 @@ class BaleBot(BaleBotGeneral): await self.send_message_helper(user=user, text=ERROR_IN_PROCESS) finally: - # user.input_query = "" + user.last_input_query = user.input_query + user.input_query = "" user.is_processing_lock = False + self.ui_handler.cancel(user) return {"ok": True} @@ -1338,7 +2027,7 @@ class BaleBot(BaleBotGeneral): if message_id is None: # اولین پیام - message_id = await self.send_message_to_bale( + message_id = await self.send_bale_message( user=user, text=full_text + " ▌", ) @@ -1346,14 +2035,14 @@ class BaleBot(BaleBotGeneral): payload = BalePayload( chat_id=user.chat_id, message_id=message_id, text=full_text + " ▌" ) - self.update_message_to_bale(user, payload) + self.update_bale_message(user, payload) # آخر کار، بدون cursor if message_id: payload = BalePayload( chat_id=user.chat_id, message_id=message_id, text=full_text ) - self.update_message_to_bale(user, payload) + self.update_bale_message(user, payload) async def handle_beta(self, user: BaleUser): """ @@ -1370,7 +2059,7 @@ class BaleBot(BaleBotGeneral): await self.send_message_helper( user=user, - structed_output=[ + structured_text=[ ["این تست است ", [[{"text": "تستتت", "callback_data": "not_yet"}]]], [ "این تست اس2ت ", @@ -1420,114 +2109,3 @@ class BaleBot(BaleBotGeneral): print(f"-- ES-Saved {es_res}") except Exception as e: print("++ failed save_to_Es ", str(traceback.print_exc())) - - -class UIState(BaleBotGeneral): - """ - مسئول نمایش «در حال تحلیل…» و پاک‌کردن پیام اصلی - در زمان لغو یا پایان کار است. - برای سرگرم کردن کاربر - یک روند کاملا مجزا برای نشان دادن پیام و حذف در انتها - """ - - def __init__( - self, - token:str, - delay_time: float = 0.4, - ): - super().__init__( - token=token, - ) - """ - :param bot: نمونهٔ BaleBot که متدهای API را در اختیار دارد - :param user: کاربری که پیامش در حال پردازش است - """ - self.delay_time = delay_time - self.done = False - self._heartbeat_task: Dict[str, asyncio.Task] = {} - - - # ------------------------------------------------------------------ - # توابعی که در والد BaleBotGeneral هستند - # ------------------------------------------------------------------ - async def delete_bale_message(self, *args, **kwargs): - await super().delete_bale_message(*args, **kwargs) - - async def send_message_helper(self, *args, **kwargs): - await super().send_message_helper(*args, **kwargs) - - - # ------------------------------------------------------------------ - # توابع خود کلاس - # ------------------------------------------------------------------ - async def active(self, user: BaleUser,): - await self.send_message_helper( - user=user, - text=self.main_text[0], - update_message=False, - is_save_active_message_id=True, - ) - - # ------------------------------------------------------------------ - # متدهای عمومی - # ------------------------------------------------------------------ - def cancel(self, user: BaleUser,): - """ - متوقف کردن حلقهٔ heartbeat و پاک‌کردن پیام اصلی - """ - self.done = True - _user_task = self._heartbeat_task.get(user.uc_id, None) - if _user_task: - self._heartbeat_task[user.uc_id].cancel() # اگر هنوز در حال اجرا باشد - # پاک‌کردن پیام اصلی - asyncio.create_task( - self.delete_bale_message( - chat_id=user.chat_id, - message_id=user.active_message_id, - ) - ) - self.main_text = ["در حال تحلیل"] - - - def create(self, user: BaleUser, main_text: str | list = ["در حال تحلیل"], waiting_list:list=[]): - """ - شروع حلقهٔ heartbeat - """ - if isinstance(main_text, str): - self.main_text = [main_text] - elif isinstance(main_text, list): - self.main_text = main_text - - print( - f'create create' - ) - self._heartbeat_task[user.uc_id] = asyncio.create_task(self.heartbeat_loop(user=user, waiting_list=waiting_list)) - - # ------------------------------------------------------------------ - # حلقهٔ heartbeat - # ------------------------------------------------------------------ - async def heartbeat_loop(self, user: BaleUser, waiting_list=[] ): - """ - هر 0.2 ثانیه متن «در حال تحلیل…» را به‌روزرسانی می‌کند. - """ - await self.active(user=user) - - if len(waiting_list) == 0: - waiting_list = [".", "..", "...", "...."] - - while not self.done: - text = random.choice(self.main_text) - - for j in waiting_list: - # ویرایش پیام - _text = f"{j} {text}" - await self.send_message_helper( - user=user, - text=_text, - update_message=True, - is_save_active_message_id=True, - ) - await asyncio.sleep(self.delay_time) - - # صبر 5 ثانیه - # await asyncio.sleep(0.2) diff --git a/router/bale/base_model.py b/router/bale/base_model.py index c2abd5c..ce8eaeb 100755 --- a/router/bale/base_model.py +++ b/router/bale/base_model.py @@ -4,34 +4,37 @@ from router.bale.bale_buttons import BUTTON_TEXT_TO_CALLBACK_LIST import json from typing import Optional, Callable, List, Any from pydantic import BaseModel +from time import time class SingleSearchData(BaseModel): - score:float - id:str - content:str + score: float + id: str + content: str + class BMNewSemanticSearchOutput(BaseModel): query: str - result : List[SingleSearchData] - metadata : Dict - embed_model_time : float - cosine_similarity_time : float - rerank_time : float - - + result: List[SingleSearchData] + metadata: Dict + embed_model_time: float + cosine_similarity_time: float + rerank_time: float + + class DbRule(BaseModel): rule_id: str rule_content: str rule_type: str section_id: str section_content: str - section_full_path :str + section_full_path: str qanon_id: str qanon_etebar: str qanon_title: str state_etebar: str + class InputRule(BaseModel): rule_id: str rule_content: str @@ -39,12 +42,23 @@ class InputRule(BaseModel): section_id: str section_content: str + class SemanticSearchP2P(BaseModel): in_rule: InputRule db_rule: DbRule score: float = 0 metadata: Dict + def __eq__(self, other): + return ( + isinstance(other, RuleRelation) + and self.db_rule.section_id == other.db_rule.section_id + ) + + def __hash__(self): + return hash(self.db_rule.section_id) + + class BaleStartMessageForm(BaseModel): id: int is_bot: bool = False @@ -181,6 +195,8 @@ class RelationIdentification(BaseModel): "تعارض مستقر", "بدون تعارض", ] + + class Evaluation(BaseModel): is_subject_unity_assessment_correct: bool is_conflict_detection_correct: bool @@ -189,6 +205,7 @@ class Evaluation(BaseModel): valid_relation_type: str comments: str + class SingleRuleRelation(BaseModel): rule_id: str rule_content: str @@ -241,15 +258,58 @@ class RuleRelation(BaseModel): # ----- relation-identification data relation_identification: RelationIdentification | None = None + def __eq__(self, other): + return ( + isinstance(other, RuleRelation) + and self.db_rule.section_id == other.db_rule.section_id + ) + + def __hash__(self): + return hash(self.db_rule.section_id) + + class StateDetail(BaseModel): - state: str + state: str message: str button_text: str end_buttons: List = [] inline_buttons: Optional[List[List[dict]]] = None - - handler : str = None - allow_empty_input: bool = True + + handler: str = None + allow_empty_input: bool = True + + +class BaleButton(BaseModel): + text: str + callback_data: str + + +class BaleMessage(BaseModel): + text: str + buttons: List[List[BaleButton]] = [] + + +class PreviousMessage(BaseModel): + message: List | str + buttons: List[List[BaleButton]] = [] + type: Literal["text", "chunked_text", "structured_text"] + + +class StackItem(BaseModel): + index: int + + step_name: str + input_data: Any | int | None = ( + None # اگر عدد بود یعنی داده از مرحله ایندکس قبلی امده + ) + output_pure_data: Any + output_formed_data: Optional[PreviousMessage] = None + + created_at: int = Field(default_factory=lambda: int(time())) + runtime_ms: float = -1 + + metadata: Dict[str, Any] = Field(default_factory=dict) + class BaleUser(BaseModel): uc_id: str @@ -261,9 +321,10 @@ class BaleUser(BaseModel): is_vip: bool = False first_name: str = "" last_name: str = "" - message_limit:int = 0 - rule_relation: RuleRelation | None = None - subject_unities:Dict = {} + message_limit: int = 0 + previous_message: PreviousMessage = None + rule_relation: RuleRelation | str | None = None + subject_unities: Dict = {} # ---- defaults effort: Literal["medium", "low"] = "low" @@ -271,11 +332,11 @@ class BaleUser(BaseModel): permission: str = "normal" # ---- runtime - is_processing_lock : bool = False - is_call_back_query : bool = False - state_detail : StateDetail = None - active_message_id : int = 0 - + is_processing_lock: bool = False + is_call_back_query: bool = False + state_detail: StateDetail = None + active_message_id: int = 0 + input_query: str = "" # ورودی کاربر last_input_query: str = "" # ورودی کاربر call_back_query: str = "" # ورودی کاربر @@ -287,10 +348,7 @@ class BaleUser(BaseModel): # ---- memory last_query: Dict[str, str] = {} # آخرین سوال کاربر last_result: Dict[str, Any] = {} # آخرین نتایج - last_runtime: Dict[str, Any] = ( - {} - ) # مثلا {"GP": {"effort": "medium", "limit": 20}} برای بقیه چیزها - stack: Dict[str, List] = {} # پشته برای داده های مرحله ای + stack: List[StackItem] = [] # پشته برای داده های مرحله ای class KeyboardItem(BaseModel): @@ -322,8 +380,7 @@ class BalePayload(BaseModel): class Step(BaseModel): name: str - level : int - data_input : str - data_output : str - bot_output : List - + level: int + data_input: str + data_output: str + bot_output: List diff --git a/router/bale/config.py b/router/bale/config.py index aacbe70..1f8b934 100644 --- a/router/bale/config.py +++ b/router/bale/config.py @@ -1,10 +1,6 @@ from router.bale.base_model import StateDetail - - - - -BUSY_TEXT = "⏳ درخواست قبلی شما در حال پردازش هست، لطفا تا اتمام آن منتظر بمانید ⏳" - +from core.static import * +from router.bale.bale_buttons import * class StateRegistry: def __init__(self, states): @@ -17,7 +13,6 @@ class StateRegistry: return list(self._states.values()) - STATE = [ StateDetail( state="search_in_law", @@ -68,12 +63,35 @@ STATE = [ message="""لطفا متن قانونی مورد نظر برای بررسی مغایرت با سیاست های کلی نظام را وارد کنید :""", handler="handle_conflict_general_policy", ), + # StateDetail( + # state="choice_conflict_all_qavanin", + # button_text="بررسی مغایرت در تمام قوانین", + # end_buttons=[ + # [{"text":B_CHOICE_SUMMARY,"callback_data":"summary_conflict_all_qavanin"}], + # [{"text":B_CHOICE_FULL,"callback_data":"full_conflict_all_qavanin"}], + # ], + # message=SM_CHOICE, + # ), StateDetail( - state="conflict_all_qavanin", + state="subject_unities_to_evalution", + button_text="بررسی مغایرت مرحله اول", + end_buttons=[], + message="""""", + handler="handle_subject_unities_to_evalution", + ), + StateDetail( + state="full_conflict_all_qavanin", button_text="بررسی مغایرت در تمام قوانین", end_buttons=[], message="""لطفا متن قانونی مورد نظر برای بررسی مغایرت در تمام قوانین جمهوری اسلامی ایران را وارد کنید :""", - handler="handle_conflict_all_qavanin", + handler="handle_full_conflict_all_qavanin", + ), + StateDetail( + state="summary_conflict_all_qavanin", + button_text="بررسی مغایرت در تمام قوانین", + end_buttons=[], + message="""لطفا متن قانونی مورد نظر برای بررسی مغایرت در تمام قوانین جمهوری اسلامی ایران را وارد کنید :""", + handler="handle_summary_conflict_all_qavanin", ), StateDetail( state="qanon_title_repeat", @@ -106,6 +124,12 @@ STATE = [ button_text="در دست توسعه", message="""این قسمت در دست توسعه قرار دارد.""", ), + StateDetail( + state="previous_message", + button_text="", + message="", + handler='handle_previous_message' + ), StateDetail( state="about_us", button_text="درباره ما ⚡", @@ -118,8 +142,10 @@ STATE = [ ), ] + STATE_REGISTERY = StateRegistry(STATE) STATE_CONFIG = {i.state: i for i in STATE} +# MAIN_BUTTON = {i.state: i for i in STATE} def build_buttons_form(button_form): @@ -132,16 +158,16 @@ def build_buttons_form(button_form): if not state: continue - row_buttons.append({ - "text": state.button_text, - "callback_data": state.state - }) + row_buttons.append( + {"text": state.button_text, "callback_data": state.state} + ) if row_buttons: buttons.append(row_buttons) return buttons + # Button-STYLE main_button_form = [ ["chat_in_law"], @@ -151,7 +177,7 @@ main_button_form = [ ["conflict_law_writing_policy"], ["conflict_qanon_asasi"], ["conflict_general_policy"], - ["conflict_all_qavanin"], - ["contact_us", "about_us", "beta"] + ["summary_conflict_all_qavanin"], + ["contact_us", "about_us", "beta"], ] -MAIN_BUTTON = build_buttons_form(main_button_form) \ No newline at end of file +MAIN_BUTTON = build_buttons_form(main_button_form)