From 0a2728b8bb73c495df9665ac34c30bb7648ab832 Mon Sep 17 00:00:00 2001 From: Rami <54779216+Ramimashkouk@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:11:52 +0300 Subject: [PATCH 1/4] feat: Integrate slots (#90) * refactor: Update to chatsky 1.0.0rc1 * refactor: Use `ast` to deal with cnd&rsp * feat: Add slot converstion * chore: Update converter to chatsky1.0.0rc1 format * chore: Strip messages before sending to ui * chore: Update autocomplet feat to 1.0.0rc1 * fix: Stop all processes when shutting down * chore: Delete handshaking with ws * style: Black up * chore: Check if slots key exists in front graph * fix: Stop process after disconnection only if running --- backend/chatsky_ui/api/api_v1/api.py | 6 +- .../chatsky_ui/api/api_v1/endpoints/bot.py | 5 +- .../{dff_services.py => chatsky_services.py} | 6 +- .../chatsky_ui/api/api_v1/endpoints/flows.py | 4 +- backend/chatsky_ui/cli.py | 3 +- .../{dff_client.py => chatsky_client.py} | 24 +- backend/chatsky_ui/main.py | 2 +- .../chatsky_ui/services/condition_finder.py | 48 ++++ backend/chatsky_ui/services/json_converter.py | 239 ++++++++---------- backend/chatsky_ui/services/process.py | 4 +- .../chatsky_ui/services/process_manager.py | 10 +- .../chatsky_ui/services/websocket_manager.py | 2 +- backend/chatsky_ui/tests/api/test_bot.py | 4 +- backend/chatsky_ui/tests/e2e/test_e2e.py | 5 +- .../tests/integration/test_api_integration.py | 5 +- backend/poetry.lock | 81 +++--- backend/pyproject.toml | 2 +- .../chatsky_ui/api/api_v1/endpoints.rst | 4 +- docs/appref/chatsky_ui/clients.rst | 4 +- 19 files changed, 251 insertions(+), 207 deletions(-) rename backend/chatsky_ui/api/api_v1/endpoints/{dff_services.py => chatsky_services.py} (91%) rename backend/chatsky_ui/clients/{dff_client.py => chatsky_client.py} (53%) create mode 100644 backend/chatsky_ui/services/condition_finder.py diff --git a/backend/chatsky_ui/api/api_v1/api.py b/backend/chatsky_ui/api/api_v1/api.py index 19eb4d1..215a4fe 100644 --- a/backend/chatsky_ui/api/api_v1/api.py +++ b/backend/chatsky_ui/api/api_v1/api.py @@ -1,11 +1,13 @@ from fastapi import APIRouter -from chatsky_ui.api.api_v1.endpoints import bot, config, dff_services, flows +from chatsky_ui.api.api_v1.endpoints import bot, config, chatsky_services, flows from chatsky_ui.core.config import settings api_router = APIRouter() api_router.include_router(config.router, prefix="/".join([settings.API_V1_STR, "config"]), tags=["config"]) api_router.include_router(flows.router, prefix="/".join([settings.API_V1_STR, "flows"]), tags=["flows"]) -api_router.include_router(dff_services.router, prefix="/".join([settings.API_V1_STR, "services"]), tags=["services"]) +api_router.include_router( + chatsky_services.router, prefix="/".join([settings.API_V1_STR, "services"]), tags=["services"] +) api_router.include_router(bot.router, prefix="/".join([settings.API_V1_STR, "bot"]), tags=["bot"]) diff --git a/backend/chatsky_ui/api/api_v1/endpoints/bot.py b/backend/chatsky_ui/api/api_v1/endpoints/bot.py index 283fd82..26620cf 100644 --- a/backend/chatsky_ui/api/api_v1/endpoints/bot.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/bot.py @@ -9,6 +9,7 @@ from chatsky_ui.services.index import Index from chatsky_ui.services.process_manager import BuildManager, ProcessManager, RunManager from chatsky_ui.services.websocket_manager import WebSocketManager +from chatsky_ui.schemas.process_status import Status router = APIRouter() @@ -264,8 +265,6 @@ async def connect( await websocket_manager.connect(websocket) run_manager.logger.info("Websocket for run process '%s' has been opened", run_id) - await websocket.send_text("Start chatting") - output_task = asyncio.create_task( websocket_manager.send_process_output_to_websocket(run_id, run_manager, websocket) ) @@ -279,3 +278,5 @@ async def connect( return_when=asyncio.FIRST_COMPLETED, ) websocket_manager.disconnect(websocket) + if await run_manager.get_status(run_id) in [Status.ALIVE, Status.RUNNING]: + await run_manager.stop(run_id) diff --git a/backend/chatsky_ui/api/api_v1/endpoints/dff_services.py b/backend/chatsky_ui/api/api_v1/endpoints/chatsky_services.py similarity index 91% rename from backend/chatsky_ui/api/api_v1/endpoints/dff_services.py rename to backend/chatsky_ui/api/api_v1/endpoints/chatsky_services.py index 5d643ff..612a8e0 100644 --- a/backend/chatsky_ui/api/api_v1/endpoints/dff_services.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/chatsky_services.py @@ -8,7 +8,7 @@ from pylint.reporters.text import TextReporter from chatsky_ui.api.deps import get_index -from chatsky_ui.clients.dff_client import get_dff_conditions +from chatsky_ui.clients.chatsky_client import get_chatsky_conditions from chatsky_ui.core.config import settings from chatsky_ui.schemas.code_snippet import CodeSnippet from chatsky_ui.services.index import Index @@ -56,5 +56,5 @@ async def lint_snippet(snippet: CodeSnippet) -> Dict[str, str]: @router.get("/get_conditions", status_code=200) async def get_conditions() -> Dict[str, Union[str, list]]: - """Gets the dff's out-of-the-box conditions.""" - return {"status": "ok", "data": get_dff_conditions()} + """Gets the chatsky's out-of-the-box conditions.""" + return {"status": "ok", "data": get_chatsky_conditions()} diff --git a/backend/chatsky_ui/api/api_v1/endpoints/flows.py b/backend/chatsky_ui/api/api_v1/endpoints/flows.py index c7168e8..8ab047b 100644 --- a/backend/chatsky_ui/api/api_v1/endpoints/flows.py +++ b/backend/chatsky_ui/api/api_v1/endpoints/flows.py @@ -10,7 +10,7 @@ @router.get("/") -async def flows_get() -> Dict[str, Union[str, Dict[str, list]]]: +async def flows_get() -> Dict[str, Union[str, Dict[str, Union[list, dict]]]]: """Get the flows by reading the frontend_flows.yaml file.""" omega_flows = await read_conf(settings.frontend_flows_path) dict_flows = OmegaConf.to_container(omega_flows, resolve=True) @@ -18,7 +18,7 @@ async def flows_get() -> Dict[str, Union[str, Dict[str, list]]]: @router.post("/") -async def flows_post(flows: Dict[str, list]) -> Dict[str, str]: +async def flows_post(flows: Dict[str, Union[list, dict]]) -> Dict[str, str]: """Write the flows to the frontend_flows.yaml file.""" await write_conf(flows, settings.frontend_flows_path) return {"status": "ok"} diff --git a/backend/chatsky_ui/cli.py b/backend/chatsky_ui/cli.py index f322fea..feb4334 100644 --- a/backend/chatsky_ui/cli.py +++ b/backend/chatsky_ui/cli.py @@ -10,7 +10,7 @@ from cookiecutter.main import cookiecutter from typing_extensions import Annotated -# Patch nest_asyncio before importing DFF +# Patch nest_asyncio before importing Chatsky nest_asyncio.apply = lambda: None from chatsky_ui.core.config import app_runner, settings # noqa: E402 @@ -179,6 +179,7 @@ def init( "https://github.com/Ramimashkouk/df_d_template.git", no_input=no_input, overwrite_if_exists=overwrite_if_exists, + checkout="feat/add-slots2" ) finally: os.chdir(original_dir) diff --git a/backend/chatsky_ui/clients/dff_client.py b/backend/chatsky_ui/clients/chatsky_client.py similarity index 53% rename from backend/chatsky_ui/clients/dff_client.py rename to backend/chatsky_ui/clients/chatsky_client.py index 9163f61..0822f70 100644 --- a/backend/chatsky_ui/clients/dff_client.py +++ b/backend/chatsky_ui/clients/chatsky_client.py @@ -1,28 +1,28 @@ from typing import List -import dff.script.conditions as cnd -from dff.pipeline.pipeline import script_parsing +import chatsky.conditions as cnd +from chatsky.core import script_parsing AUTO_COMPLETION_MAP = { - "exact_match": 'cnd.exact_match(Message("hello"))(ctx, pipeline)', - "regexp": 'cnd.regexp(r"how are you", re.IGNORECASE)(ctx, pipeline)', - "any": 'cnd.any([hi_lower_case_condition, cnd.exact_match(Message("hello"))])(ctx, pipeline)', - "all": 'cnd.all([cnd.regexp(r"talk"), cnd.regexp(r"about.*music")])(ctx, pipeline)', + "ExactMatch": 'await cnd.ExactMatch("hello")(ctx)', # TODO: delete Message as it's redundant in the new version + "Regexp": 'await cnd.Regexp("how are you")(ctx)', + "Any": "cnd.Any([hi_lower_case_condition, cnd.ExactMatch(hello)])(ctx)", # TODO: the same + "All": 'cnd.All([cnd.Regexp("talk"), cnd.Regexp("about.*music")])(ctx)', } -def get_dff_conditions() -> List[dict]: - """Gets the DFF's out-of-the-box conditions. +def get_chatsky_conditions() -> List[dict]: + """Gets the Chatsky's out-of-the-box conditions. Returns: List of conditions info with the following keys: "label": The condition name suggestions to pop up for user. "type": "function". - "info": Detailed info about every condition, parsed from DFF docs. + "info": Detailed info about every condition, parsed from Chatsky docs. "apply": Autocompletion of the conditon call. """ - native_services = script_parsing.get_dff_objects() - native_conditions = [k.split(".")[-1] for k, _ in native_services.items() if k.startswith("dff.cnd.")] + native_services = script_parsing.get_chatsky_objects() + native_conditions = [k.split(".")[-1] for k, _ in native_services.items() if k.startswith("chatsky.cnd.")] cnd_full_info = [] for condition in native_conditions: cnd_full_info.append( @@ -30,7 +30,7 @@ def get_dff_conditions() -> List[dict]: "label": f"cnd.{condition}", "type": "function", "info": getattr(cnd, condition).__doc__, - "apply": AUTO_COMPLETION_MAP.get(condition, "cnd.()(ctx, pipeline)"), + "apply": AUTO_COMPLETION_MAP.get(condition, "cnd.()(ctx, pipeline)"), } ) diff --git a/backend/chatsky_ui/main.py b/backend/chatsky_ui/main.py index 05e890b..423778d 100644 --- a/backend/chatsky_ui/main.py +++ b/backend/chatsky_ui/main.py @@ -20,7 +20,7 @@ async def lifespan(app: FastAPI): await index_dict["instance"].load() yield - settings.temp_conf.unlink(missing_ok=True) + # settings.temp_conf.unlink(missing_ok=True) app = FastAPI(title="DF Designer", lifespan=lifespan) diff --git a/backend/chatsky_ui/services/condition_finder.py b/backend/chatsky_ui/services/condition_finder.py new file mode 100644 index 0000000..a60b998 --- /dev/null +++ b/backend/chatsky_ui/services/condition_finder.py @@ -0,0 +1,48 @@ +import ast +from ast import NodeTransformer +from typing import List, Dict + +from chatsky_ui.core.logger_config import get_logger + +logger = get_logger(__name__) + + +class ServiceReplacer(NodeTransformer): + def __init__(self, new_services: List[str]): + self.new_services_classes = self._get_classes_def(new_services) + + def _get_classes_def(self, services_code: List[str]) -> Dict[str, ast.ClassDef]: + parsed_codes = [ast.parse(service_code) for service_code in services_code] + result_nodes = {} + for idx, parsed_code in enumerate(parsed_codes): + self._extract_class_defs(parsed_code, result_nodes, services_code[idx]) + return result_nodes + + def _extract_class_defs(self, parsed_code: ast.Module, result_nodes: Dict[str, ast.ClassDef], service_code: str): + for node in parsed_code.body: + if isinstance(node, ast.ClassDef): + result_nodes[node.name] = node + else: + logger.error("No class definition found in new_service: %s", service_code) + + def visit_ClassDef(self, node: ast.ClassDef) -> ast.ClassDef: + logger.debug("Visiting class '%s' and comparing with: %s", node.name, self.new_services_classes.keys()) + if node.name in self.new_services_classes: + return self._get_class_def(node) + return node + + def _get_class_def(self, node: ast.ClassDef) -> ast.ClassDef: + service = self.new_services_classes[node.name] + del self.new_services_classes[node.name] + return service + + def generic_visit(self, node: ast.AST): + super().generic_visit(node) + if isinstance(node, ast.Module) and self.new_services_classes: + self._append_new_services(node) + return node + + def _append_new_services(self, node: ast.Module): + logger.info("Services not found, appending new services: %s", list(self.new_services_classes.keys())) + for _, service in self.new_services_classes.items(): + node.body.append(service) diff --git a/backend/chatsky_ui/services/json_converter.py b/backend/chatsky_ui/services/json_converter.py index d0d7a77..9fe3316 100644 --- a/backend/chatsky_ui/services/json_converter.py +++ b/backend/chatsky_ui/services/json_converter.py @@ -2,24 +2,29 @@ JSON Converter --------------- -Converts a user project's frontend graph to a script understandable by DFF json-importer. +Converts a user project's frontend graph to a script understandable by Chatsky json-importer. """ +import ast from pathlib import Path from typing import List, Optional, Tuple +from collections import defaultdict from omegaconf.dictconfig import DictConfig -from chatsky_ui.api.deps import get_index from chatsky_ui.core.logger_config import get_logger from chatsky_ui.core.config import settings from chatsky_ui.db.base import read_conf, write_conf -from chatsky_ui.services.index import Index +from chatsky_ui.services.condition_finder import ServiceReplacer + logger = get_logger(__name__) +PRE_TRANSITION = "PRE_TRANSITION" + + def _get_db_paths(build_id: int) -> Tuple[Path, Path, Path, Path]: - """Get paths to frontend graph, dff script, and dff custom conditions files.""" + """Get paths to frontend graph, chatsky script, and chatsky custom conditions files.""" frontend_graph_path = settings.frontend_flows_path custom_conditions_file = settings.conditions_path custom_responses_file = settings.responses_path @@ -38,20 +43,20 @@ def _get_db_paths(build_id: int) -> Tuple[Path, Path, Path, Path]: return frontend_graph_path, script_path, custom_conditions_file, custom_responses_file -def _organize_graph_according_to_nodes(flow_graph: DictConfig, script: dict) -> dict: +def _organize_graph_according_to_nodes(flow_graph: DictConfig, script: dict) -> Tuple[dict, dict]: nodes = {} for flow in flow_graph["flows"]: node_names_in_one_flow = [] for node in flow.data.nodes: if "flags" in node.data: if "start" in node.data.flags: - if "start_label" in script["CONFIG"]: + if "start_label" in script: raise ValueError("There are more than one start node in the script") - script["CONFIG"]["start_label"] = [flow.name, node.data.name] + script["start_label"] = [flow.name, node.data.name] if "fallback" in node.data.flags: - if "fallback_label" in script["CONFIG"]: + if "fallback_label" in script: raise ValueError("There are more than one fallback node in the script") - script["CONFIG"]["fallback_label"] = [flow.name, node.data.name] + script["fallback_label"] = [flow.name, node.data.name] if node.data.name in node_names_in_one_flow: raise ValueError(f"There is more than one node with the name '{node.data.name}' in the same flow.") @@ -59,7 +64,24 @@ def _organize_graph_according_to_nodes(flow_graph: DictConfig, script: dict) -> nodes[node.id] = {"info": node} nodes[node.id]["flow"] = flow.name nodes[node.id]["TRANSITIONS"] = [] - return nodes + nodes[node.id][PRE_TRANSITION] = dict() + + def _convert_slots(slots: dict) -> dict: + group_slot = defaultdict(dict) + for slot_name, slot_values in slots.copy().items(): + slot_type = slot_values["type"] + del slot_values["id"] + del slot_values["type"] + if slot_type != "GroupSlot": + group_slot[slot_name].update({f"chatsky.slots.{slot_type}": {k: v for k, v in slot_values.items()}}) + else: + group_slot[slot_name] = _convert_slots(slot_values) + return dict(group_slot) + + if "slots" in flow_graph: + script["slots"] = _convert_slots(flow_graph["slots"]) + + return nodes, script def _get_condition(nodes: dict, edge: DictConfig) -> Optional[DictConfig]: @@ -70,18 +92,36 @@ def _get_condition(nodes: dict, edge: DictConfig) -> Optional[DictConfig]: ) -def _write_list_to_file(conditions_lines: list, custom_conditions_file: Path) -> None: - """Write dff custom conditions from list to file.""" - # TODO: make reading and writing conditions async - with open(custom_conditions_file, "w", encoding="UTF-8") as file: - for line in conditions_lines: - if not line.endswith("\n"): - line = "".join([line, "\n"]) - file.write(line) +def _add_transitions(nodes: dict, edge: DictConfig, condition: DictConfig, slots: DictConfig) -> None: + """Add transitions to a node according to `edge` and `condition`.""" + def _get_slot(slots, id_): + if not slots: + return "" + for name, value in slots.copy().items(): + slot_path = name + if value.get("id") == id_: + return name + elif value.get("type") != "GroupSlot": + continue + else: + del value["id"] + del value["type"] + slot_path = _get_slot(value, id_) + if slot_path: + slot_path = ".".join([name, slot_path]) + return slot_path + + if condition["type"] == "python": + converted_cnd = {f"custom.conditions.{condition.name}": None} + elif condition["type"] == "slot": + slot = _get_slot(slots, id_=condition.data.slot) + converted_cnd = {"chatsky.conditions.slots.SlotsExtracted": slot} + nodes[edge.source][PRE_TRANSITION].update({slot: {"chatsky.processing.slots.Extract": slot}}) + # TODO: elif condition["type"] == "chatsky": + else: + raise ValueError(f"Unknown condition type: {condition['type']}") -def _add_transitions(nodes: dict, edge: DictConfig, condition: DictConfig) -> None: - """Add transitions to a node according to `edge` and `condition`.""" # if the edge is a link_node, we add transition of its source and target if nodes[edge.target]["info"].type == "link_node": flow = nodes[edge.target]["info"].data.transition.target_flow @@ -91,107 +131,45 @@ def _add_transitions(nodes: dict, edge: DictConfig, condition: DictConfig) -> No else: flow = nodes[edge.target]["flow"] node = nodes[edge.target]["info"].data.name + nodes[edge.source]["TRANSITIONS"].append( { - "lbl": [ + "dst": [ flow, node, - condition.data.priority, ], - "cnd": f"custom_dir.conditions.{condition.name}", + "priority": condition.data.priority, + "cnd": converted_cnd, } ) def _fill_nodes_into_script(nodes: dict, script: dict) -> None: - """Fill nodes into dff script dictunary.""" + """Fill nodes into chatsky script dictunary.""" for _, node in nodes.items(): if node["info"].type == "link_node": continue - if node["flow"] not in script: - script[node["flow"]] = {} - script[node["flow"]].update( + if node["flow"] not in script["script"]: + script["script"][node["flow"]] = {} + script["script"][node["flow"]].update( { node["info"].data.name: { "RESPONSE": node["info"].data.response, "TRANSITIONS": node["TRANSITIONS"], + PRE_TRANSITION: node[PRE_TRANSITION], } } ) -def _append(service: DictConfig, services_lines: list) -> list: - """Append a condition to a list""" - if service.type == "python": - service_with_newline = "".join([service.data.python.action + "\n\n"]) - - logger.debug("Service to append: %s", service_with_newline) - logger.debug("services_lines before appending: %s", services_lines) - - all_lines = services_lines + service_with_newline.split("\n") - return all_lines - - -async def _shift_cnds_in_index(index: Index, cnd_strt_lineno: int, diff_in_lines: int) -> None: - """Update the start line number of conditions in index by shifting them by `diff_in_lines`.""" - services = index.get_services() - for _, service in services.items(): - if service["type"] == "condition": - if service["lineno"] - 1 > cnd_strt_lineno: # -1 is here to convert from file numeration to list numeration - service["lineno"] += diff_in_lines - - await index.indexit_all( - [service_name for service_name, _ in services.items()], - [service["type"] for _, service in services.items()], - [service["lineno"] for _, service in services.items()], - ) - - -async def _replace(service: DictConfig, services_lines: list, cnd_strt_lineno: int, index: Index) -> list: - """Replace a servuce in a services list with a new one. - - Args: - service: service to replace. `condition.data.python.action` is a string with the new service(condition) - conditions_lines: list of conditions lines - cnd_strt_lineno: a pointer to the service start line in custom conditions file - index: index object to update - - Returns: - list of all conditions as lines - """ - cnd_strt_lineno = cnd_strt_lineno - 1 # conversion from file numeration to list numeration - all_lines = services_lines.copy() - if service.type == "python": - condition = "".join([service.data.python.action + "\n\n"]) - new_cnd_lines = condition.split("\n") - - old_cnd_lines_num = 0 - for lineno, line in enumerate(all_lines[cnd_strt_lineno:]): - if line.startswith("def ") and lineno != 0: - break - old_cnd_lines_num += 1 - - next_func_location = cnd_strt_lineno + old_cnd_lines_num - - logger.debug("new_cnd_lines\n") - logger.debug(new_cnd_lines) - all_lines = all_lines[:cnd_strt_lineno] + new_cnd_lines + all_lines[next_func_location:] - - diff_in_lines = len(new_cnd_lines) - old_cnd_lines_num - logger.debug("diff_in_lines: %s", diff_in_lines) - logger.debug("cnd_strt_lineno: %s", cnd_strt_lineno) - - await _shift_cnds_in_index(index, cnd_strt_lineno, diff_in_lines) - return all_lines - - -async def update_responses_lines(nodes: dict, responses_lines: list, index: Index) -> Tuple[dict, List[str]]: +async def update_responses_lines(nodes: dict) -> Tuple[dict, List[str]]: """Organizes the responses in nodes in a format that json-importer accepts. If the response type is "python", its function will be added to responses_lines to be written to the custom_conditions_file later. * If the response already exists in the responses_lines, it will be replaced with the new one. """ + responses_list = [] for node in nodes.values(): if node["info"].type == "link_node": continue @@ -199,55 +177,53 @@ async def update_responses_lines(nodes: dict, responses_lines: list, index: Inde logger.debug("response type: %s", response.type) if response.type == "python": response.data = response.data[0] - if response.name not in (rsp_names := index.index): - logger.debug("Adding response: %s", response.name) - rsp_lineno = len(responses_lines) - responses_lines = _append(response, responses_lines) - await index.indexit(response.name, "response", rsp_lineno + 1) - else: - logger.debug("Replacing response: %s", response.name) - responses_lines = await _replace(response, responses_lines, rsp_names[response.name]["lineno"], index) - node["info"].data.response = f"custom_dir.responses.{response.name}" + logger.info("Adding response: %s", response) + + responses_list.append(response.data.python.action) + node["info"].data.response = {f"custom.responses.{response.name}": None} elif response.type == "text": response.data = response.data[0] logger.debug("Adding response: %s", response.data.text) - node["info"].data.response = {"dff.Message": {"text": response.data.text}} + node["info"].data.response = {"chatsky.Message": {"text": response.data.text}} elif response.type == "choice": # logger.debug("Adding response: %s", ) - dff_responses = [] + chatsky_responses = [] for message in response.data: if "text" in message: - dff_responses.append({"dff.Message": {"text": message["text"]}}) - else: + chatsky_responses.append({"chatsky.Message": {"text": message["text"]}}) + else: # TODO: check: are you sure that you can use only "text" type inside a choice? raise ValueError("Unknown response type. There must be a 'text' field in each message.") - node["info"].data.response = {"dff.rsp.choice": dff_responses.copy()} + node["info"].data.response = {"chatsky.rsp.choice": chatsky_responses.copy()} else: raise ValueError(f"Unknown response type: {response.type}") - return nodes, responses_lines + return nodes, responses_list async def converter(build_id: int) -> None: - """Translate frontend flow script into dff script.""" - index = get_index() - await index.load() - index.logger.debug("Loaded index '%s'", index.index) - + """Translate frontend flow script into chatsky script.""" frontend_graph_path, script_path, custom_conditions_file, custom_responses_file = _get_db_paths(build_id) - script = { - "CONFIG": {"custom_dir": str("/" / settings.custom_dir)}, - } + script = {"script": {}} flow_graph: DictConfig = await read_conf(frontend_graph_path) # type: ignore - nodes = _organize_graph_according_to_nodes(flow_graph, script) + nodes, script = _organize_graph_according_to_nodes(flow_graph, script) with open(custom_responses_file, "r", encoding="UTF-8") as file: - responses_lines = file.readlines() + responses_tree = ast.parse(file.read()) + + nodes, responses_list = await update_responses_lines(nodes) - nodes, responses_lines = await update_responses_lines(nodes, responses_lines, index) + logger.info("Responses list: %s", responses_list) + replacer = ServiceReplacer(responses_list) + replacer.visit(responses_tree) + + with open(custom_responses_file, "w") as file: + file.write(ast.unparse(responses_tree)) with open(custom_conditions_file, "r", encoding="UTF-8") as file: - conditions_lines = file.readlines() + conditions_tree = ast.parse(file.read()) + + conditions_list = [] for flow in flow_graph["flows"]: for edge in flow.data.edges: @@ -261,24 +237,19 @@ async def converter(build_id: int) -> None: edge.sourceHandle, ) continue + if condition.type == "python": + conditions_list.append(condition.data.python.action) - if condition.name not in (cnd_names := index.index): - logger.debug("Adding condition: %s", condition.name) - cnd_lineno = len(conditions_lines) - conditions_lines = _append(condition, conditions_lines) - await index.indexit(condition.name, "condition", cnd_lineno + 1) - else: - logger.debug("Replacing condition: %s", condition.name) - conditions_lines = await _replace( - condition, conditions_lines, cnd_names[condition.name]["lineno"], index - ) - - _add_transitions(nodes, edge, condition) + _add_transitions(nodes, edge, condition, flow_graph["slots"]) else: logger.error("A node of edge '%s-%s' is not found in nodes", edge.source, edge.target) + replacer = ServiceReplacer(conditions_list) + replacer.visit(conditions_tree) + + with open(custom_conditions_file, "w") as file: + file.write(ast.unparse(conditions_tree)) + _fill_nodes_into_script(nodes, script) - _write_list_to_file(conditions_lines, custom_conditions_file) - _write_list_to_file(responses_lines, custom_responses_file) await write_conf(script, script_path) diff --git a/backend/chatsky_ui/services/process.py b/backend/chatsky_ui/services/process.py index 9ad1852..1a04220 100644 --- a/backend/chatsky_ui/services/process.py +++ b/backend/chatsky_ui/services/process.py @@ -195,7 +195,7 @@ async def is_alive(self) -> bool: class RunProcess(Process): - """Process for running a DFF pipeline.""" + """Process for running a Chatsky pipeline.""" def __init__(self, id_: int, build_id: int, preset_end_status: str = ""): super().__init__(id_, preset_end_status) @@ -239,7 +239,7 @@ async def update_db_info(self) -> None: class BuildProcess(Process): - """Process for converting a frontned graph to a DFF script.""" + """Process for converting a frontned graph to a Chatsky script.""" def __init__(self, id_: int, preset_end_status: str = ""): super().__init__(id_, preset_end_status) diff --git a/backend/chatsky_ui/services/process_manager.py b/backend/chatsky_ui/services/process_manager.py index bb9e1d1..3a6aece 100644 --- a/backend/chatsky_ui/services/process_manager.py +++ b/backend/chatsky_ui/services/process_manager.py @@ -55,6 +55,12 @@ async def stop(self, id_: int) -> None: except (RuntimeError, ProcessLookupError): raise + async def stop_all(self) -> None: + self.logger.info("Stopping all process %s", self.processes) + for id_, process in self.processes.items(): + if process.process.returncode is None: + await self.stop(id_) + async def check_status(self, id_: int, *args, **kwargs) -> None: """Checks the status of the process with the given id by calling the `periodically_check_status` method of the process. @@ -104,7 +110,7 @@ async def fetch_process_logs(self, id_: int, offset: int, limit: int, path: Path class RunManager(ProcessManager): - """Process manager for running a DFF pipeline.""" + """Process manager for running a Chatsky pipeline.""" async def start(self, build_id: int, preset: Preset) -> int: """Starts a new run process. @@ -152,7 +158,7 @@ async def fetch_run_logs(self, run_id: int, offset: int, limit: int) -> Optional class BuildManager(ProcessManager): - """Process manager for converting a frontned graph to a DFF script.""" + """Process manager for converting a frontned graph to a Chatsky script.""" async def start(self, preset: Preset) -> int: """Starts a new build process. diff --git a/backend/chatsky_ui/services/websocket_manager.py b/backend/chatsky_ui/services/websocket_manager.py index e1229c6..70b57b8 100644 --- a/backend/chatsky_ui/services/websocket_manager.py +++ b/backend/chatsky_ui/services/websocket_manager.py @@ -56,7 +56,7 @@ async def send_process_output_to_websocket( response = await process_manager.processes[run_id].read_stdout() if not response: break - await websocket.send_text(response.decode().strip()) + await websocket.send_text(response.decode().strip().split("text=")[-1].strip("'")) except WebSocketDisconnect: self.logger.info("Websocket connection is closed by client") except RuntimeError: diff --git a/backend/chatsky_ui/tests/api/test_bot.py b/backend/chatsky_ui/tests/api/test_bot.py index 164e27d..f50529c 100644 --- a/backend/chatsky_ui/tests/api/test_bot.py +++ b/backend/chatsky_ui/tests/api/test_bot.py @@ -157,8 +157,8 @@ async def test_connect(mocker): websocket = mocker.AsyncMock() websocket_manager = mocker.AsyncMock() websocket_manager.disconnect = mocker.MagicMock() - run_manager = mocker.MagicMock() - run_process = mocker.MagicMock() + run_manager = mocker.AsyncMock() + run_process = mocker.AsyncMock() run_manager.processes = {RUN_ID: run_process} mocker.patch.object(websocket, "query_params", {"run_id": str(RUN_ID)}) diff --git a/backend/chatsky_ui/tests/e2e/test_e2e.py b/backend/chatsky_ui/tests/e2e/test_e2e.py index 38ccd98..535c28f 100644 --- a/backend/chatsky_ui/tests/e2e/test_e2e.py +++ b/backend/chatsky_ui/tests/e2e/test_e2e.py @@ -50,6 +50,5 @@ async def test_all(mocker): await asyncio.sleep(10) assert await process_manager.get_status(run_id) == Status.ALIVE - async with aconnect_ws(f"http://localhost:8000/api/v1/bot/run/connect?run_id={run_id}", client) as ws: - message = await ws.receive_text() - assert message == "Start chatting" + async with aconnect_ws(f"http://localhost:8000/api/v1/bot/run/connect?run_id={run_id}", client): + pass diff --git a/backend/chatsky_ui/tests/integration/test_api_integration.py b/backend/chatsky_ui/tests/integration/test_api_integration.py index 81a1aa6..6ab9716 100644 --- a/backend/chatsky_ui/tests/integration/test_api_integration.py +++ b/backend/chatsky_ui/tests/integration/test_api_integration.py @@ -205,9 +205,8 @@ async def test_connect_to_ws(mocker): assert await process_manager.get_status(run_id) == Status.ALIVE - async with aconnect_ws(f"http://localhost:8000/api/v1/bot/run/connect?run_id={run_id}", client) as ws: - message = await ws.receive_text() - assert message == "Start chatting" + async with aconnect_ws(f"http://localhost:8000/api/v1/bot/run/connect?run_id={run_id}", client): + pass def test_search_service(client): diff --git a/backend/poetry.lock b/backend/poetry.lock index 84ec8d6..4420c16 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -288,6 +288,40 @@ files = [ {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] +[[package]] +name = "chatsky" +version = "1.0.0rc1" +description = "Chatsky is a free and open-source software stack for creating chatbots, released under the terms of Apache License 2.0." +optional = false +python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" +files = [ + {file = "chatsky-1.0.0rc1-py3-none-any.whl", hash = "sha256:cd2ab29aa814d1719d68ad8e2245ced165fa3959143b50e4e4347a0cc9887339"}, + {file = "chatsky-1.0.0rc1.tar.gz", hash = "sha256:e6f19886b5d33c2a3f7f96f06f1965ebec875179683fcdda5ca90e1323973a23"}, +] + +[package.dependencies] +colorama = "*" +eval_type_backport = "*" +nest-asyncio = "*" +pydantic = ">=2.0" +pyyaml = {version = "*", optional = true, markers = "extra == \"yaml\""} +typing-extensions = "*" +wrapt = "*" + +[package.extras] +benchmark = ["altair", "humanize", "pandas", "pympler", "tqdm"] +json = ["aiofiles"] +mongodb = ["motor"] +mysql = ["asyncmy", "cryptography", "sqlalchemy[asyncio]"] +pickle = ["aiofiles"] +postgresql = ["asyncpg", "sqlalchemy[asyncio]"] +redis = ["redis"] +sqlite = ["aiosqlite", "sqlalchemy[asyncio]"] +stats = ["omegaconf", "opentelemetry-exporter-otlp (>=1.20.0)", "opentelemetry-instrumentation", "requests", "tqdm"] +telegram = ["python-telegram-bot[all] (>=21.3,<22.0)"] +yaml = ["pyyaml"] +ydb = ["six", "ydb"] + [[package]] name = "click" version = "8.1.7" @@ -334,37 +368,6 @@ pyyaml = ">=5.3.1" requests = ">=2.23.0" rich = "*" -[[package]] -name = "dff" -version = "0.6.4.dev0" -description = "Dialog Flow Framework is a free and open-source software stack for creating chatbots, released under the terms of Apache License 2.0." -optional = false -python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" -files = [ - {file = "dff-0.6.4.dev0-py3-none-any.whl", hash = "sha256:2bf1375bdde25492f623995bb148773d5f99b0173d547f9f7a47aa351d2a6302"}, - {file = "dff-0.6.4.dev0.tar.gz", hash = "sha256:06a44f7e43e137208bc02615e95492526afc20a9078688d5be34fdb01934bd64"}, -] - -[package.dependencies] -colorama = "*" -nest-asyncio = "*" -pydantic = ">=2.0" -typing-extensions = "*" -wrapt = "*" - -[package.extras] -benchmark = ["altair", "humanize", "pandas", "pympler", "tqdm"] -json = ["aiofiles"] -mongodb = ["motor"] -mysql = ["asyncmy", "cryptography", "sqlalchemy[asyncio]"] -pickle = ["aiofiles"] -postgresql = ["asyncpg", "sqlalchemy[asyncio]"] -redis = ["redis"] -sqlite = ["aiosqlite", "sqlalchemy[asyncio]"] -stats = ["omegaconf", "opentelemetry-exporter-otlp (>=1.20.0)", "opentelemetry-instrumentation", "requests", "tqdm"] -telegram = ["pytelegrambotapi"] -ydb = ["six", "ydb"] - [[package]] name = "dill" version = "0.3.8" @@ -391,6 +394,20 @@ files = [ {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, ] +[[package]] +name = "eval-type-backport" +version = "0.2.0" +description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." +optional = false +python-versions = ">=3.8" +files = [ + {file = "eval_type_backport-0.2.0-py3-none-any.whl", hash = "sha256:ac2f73d30d40c5a30a80b8739a789d6bb5e49fdffa66d7912667e2015d9c9933"}, + {file = "eval_type_backport-0.2.0.tar.gz", hash = "sha256:68796cfbc7371ebf923f03bdf7bef415f3ec098aeced24e054b253a0e78f7b37"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "exceptiongroup" version = "1.2.0" @@ -1911,4 +1928,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "f80671aa36a35cf0f6a92e876da44666b09ff84dbe0fe8f022c74f420fc63ac9" +content-hash = "7095f314c771bc5604cd35b3be6c42780f46c0be413d65296e4e0aed5a4aa771" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 4b95214..983e5ee 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -20,7 +20,7 @@ typer = "^0.9.0" pydantic-settings = "^2.2.1" aiofiles = "^23.2.1" cookiecutter = "^2.6.0" -dff = "==0.6.4.dev0" +chatsky = {version = "1.0.0rc1", extras = ["yaml"]} omegaconf = "^2.3.0" pytest = "^8.1.1" pytest-asyncio = "^0.23.6" diff --git a/docs/appref/chatsky_ui/api/api_v1/endpoints.rst b/docs/appref/chatsky_ui/api/api_v1/endpoints.rst index f450bd5..761947a 100644 --- a/docs/appref/chatsky_ui/api/api_v1/endpoints.rst +++ b/docs/appref/chatsky_ui/api/api_v1/endpoints.rst @@ -9,10 +9,10 @@ chatsky_ui.api.api\_v1.endpoints.bot module :undoc-members: :show-inheritance: -chatsky_ui.api.api\_v1.endpoints.dff\_services module +chatsky_ui.api.api\_v1.endpoints.chatsky\_services module -------------------------------------- -.. automodule:: chatsky_ui.api.api_v1.endpoints.dff_services +.. automodule:: chatsky_ui.api.api_v1.endpoints.chatsky_services :members: :undoc-members: :show-inheritance: diff --git a/docs/appref/chatsky_ui/clients.rst b/docs/appref/chatsky_ui/clients.rst index 0a4b419..c21ba4f 100644 --- a/docs/appref/chatsky_ui/clients.rst +++ b/docs/appref/chatsky_ui/clients.rst @@ -1,10 +1,10 @@ chatsky_ui.clients package =================== -chatsky_ui.clients.dff module +chatsky_ui.clients.chatsky_client module ---------------------- -.. automodule:: chatsky_ui.clients.dff +.. automodule:: chatsky_ui.clients.chatsky_client :members: :undoc-members: :show-inheritance: From 7013d0a8ddb84a13e653c8e2b1435ce8d1f5ab34 Mon Sep 17 00:00:00 2001 From: Rami <54779216+Ramimashkouk@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:26:19 +0300 Subject: [PATCH 2/4] feat: Support telegram interface (#91) * feat: Support telegram interface * fix: Include automatic version in fastapi app --- backend/chatsky_ui/clients/chatsky_client.py | 4 +- backend/chatsky_ui/main.py | 3 +- backend/chatsky_ui/services/json_converter.py | 32 +- backend/poetry.lock | 355 +++++++++++++++++- backend/pyproject.toml | 2 +- 5 files changed, 390 insertions(+), 6 deletions(-) diff --git a/backend/chatsky_ui/clients/chatsky_client.py b/backend/chatsky_ui/clients/chatsky_client.py index 0822f70..4dfdca1 100644 --- a/backend/chatsky_ui/clients/chatsky_client.py +++ b/backend/chatsky_ui/clients/chatsky_client.py @@ -4,9 +4,9 @@ from chatsky.core import script_parsing AUTO_COMPLETION_MAP = { - "ExactMatch": 'await cnd.ExactMatch("hello")(ctx)', # TODO: delete Message as it's redundant in the new version + "ExactMatch": 'await cnd.ExactMatch("hello")(ctx)', "Regexp": 'await cnd.Regexp("how are you")(ctx)', - "Any": "cnd.Any([hi_lower_case_condition, cnd.ExactMatch(hello)])(ctx)", # TODO: the same + "Any": "cnd.Any([hi_lower_case_condition, cnd.ExactMatch(hello)])(ctx)", "All": 'cnd.All([cnd.Regexp("talk"), cnd.Regexp("about.*music")])(ctx)', } diff --git a/backend/chatsky_ui/main.py b/backend/chatsky_ui/main.py index 423778d..8bb1036 100644 --- a/backend/chatsky_ui/main.py +++ b/backend/chatsky_ui/main.py @@ -4,6 +4,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse +from chatsky_ui import __version__ from chatsky_ui.api.api_v1.api import api_router from chatsky_ui.api.deps import get_index from chatsky_ui.core.config import settings @@ -23,7 +24,7 @@ async def lifespan(app: FastAPI): # settings.temp_conf.unlink(missing_ok=True) -app = FastAPI(title="DF Designer", lifespan=lifespan) +app = FastAPI(title="DF Designer", version=__version__, lifespan=lifespan) app.add_middleware( diff --git a/backend/chatsky_ui/services/json_converter.py b/backend/chatsky_ui/services/json_converter.py index 9fe3316..4c52222 100644 --- a/backend/chatsky_ui/services/json_converter.py +++ b/backend/chatsky_ui/services/json_converter.py @@ -199,12 +199,42 @@ async def update_responses_lines(nodes: dict) -> Tuple[dict, List[str]]: return nodes, responses_list +def map_interface(interface: DictConfig) -> dict: + """Map frontend interface to chatsky interface.""" + if not isinstance(interface, DictConfig): + raise ValueError(f"Interface must be a dictionary. Got: {type(interface)}") + keys = interface.keys() + if len(keys)!=1: + raise ValueError("There must be only one key in the interface") + + key = next(iter(keys)) + if key == "telegram": + if "token" not in interface[key]: + raise ValueError("Token keyworkd is not provided for telegram interface") + if not interface[key]["token"]: + raise ValueError("Token is not provided for telegram interface") + return { + "chatsky.messengers.telegram.LongpollingInterface": { + "token": interface[key]["token"] + } + } + if key == "cli": + return { + "chatsky.messengers.console.CLIMessengerInterface": {} + } + else: + raise ValueError(f"Unknown interface: {key}") + async def converter(build_id: int) -> None: """Translate frontend flow script into chatsky script.""" frontend_graph_path, script_path, custom_conditions_file, custom_responses_file = _get_db_paths(build_id) - script = {"script": {}} flow_graph: DictConfig = await read_conf(frontend_graph_path) # type: ignore + script = { + "script": {}, + "messenger_interface": map_interface(flow_graph["interface"]), + } + del flow_graph["interface"] nodes, script = _organize_graph_according_to_nodes(flow_graph, script) diff --git a/backend/poetry.lock b/backend/poetry.lock index 4420c16..a8ad839 100644 --- a/backend/poetry.lock +++ b/backend/poetry.lock @@ -11,6 +11,17 @@ files = [ {file = "aiofiles-23.2.1.tar.gz", hash = "sha256:84ec2218d8419404abcb9f0c02df3f34c6e0a68ed41072acfb1cef5cbc29051a"}, ] +[[package]] +name = "aiolimiter" +version = "1.1.0" +description = "asyncio rate limiter, a leaky bucket implementation" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "aiolimiter-1.1.0-py3-none-any.whl", hash = "sha256:0b4997961fc58b8df40279e739f9cf0d3e255e63e9a44f64df567a8c17241e24"}, + {file = "aiolimiter-1.1.0.tar.gz", hash = "sha256:461cf02f82a29347340d031626c92853645c099cb5ff85577b831a7bd21132b5"}, +] + [[package]] name = "alabaster" version = "0.7.13" @@ -68,6 +79,34 @@ doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphin test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] trio = ["trio (>=0.23)"] +[[package]] +name = "apscheduler" +version = "3.10.4" +description = "In-process task scheduler with Cron-like capabilities" +optional = false +python-versions = ">=3.6" +files = [ + {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, + {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, +] + +[package.dependencies] +pytz = "*" +six = ">=1.4.0" +tzlocal = ">=2.0,<3.dev0 || >=4.dev0" + +[package.extras] +doc = ["sphinx", "sphinx-rtd-theme"] +gevent = ["gevent"] +mongodb = ["pymongo (>=3.0)"] +redis = ["redis (>=3.0)"] +rethinkdb = ["rethinkdb (>=2.4.0)"] +sqlalchemy = ["sqlalchemy (>=1.4)"] +testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] +tornado = ["tornado (>=4.3)"] +twisted = ["twisted"] +zookeeper = ["kazoo"] + [[package]] name = "arrow" version = "1.3.0" @@ -118,6 +157,34 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [package.extras] dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] +[[package]] +name = "backports-zoneinfo" +version = "0.2.1" +description = "Backport of the standard library zoneinfo module" +optional = false +python-versions = ">=3.6" +files = [ + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win32.whl", hash = "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08"}, + {file = "backports.zoneinfo-0.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win32.whl", hash = "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b"}, + {file = "backports.zoneinfo-0.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win32.whl", hash = "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328"}, + {file = "backports.zoneinfo-0.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6"}, + {file = "backports.zoneinfo-0.2.1.tar.gz", hash = "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2"}, +] + +[package.extras] +tzdata = ["tzdata"] + [[package]] name = "binaryornot" version = "0.4.4" @@ -167,6 +234,17 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "cachetools" +version = "5.5.0" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.5.0-py3-none-any.whl", hash = "sha256:02134e8439cdc2ffb62023ce1debca2944c3f289d66bb17ead3ab3dede74b292"}, + {file = "cachetools-5.5.0.tar.gz", hash = "sha256:2cc24fb4cbe39633fb7badd9db9ca6295d766d9c2995f245725a46715d050f2a"}, +] + [[package]] name = "certifi" version = "2024.2.2" @@ -178,6 +256,85 @@ files = [ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + [[package]] name = "chardet" version = "5.2.0" @@ -304,6 +461,7 @@ colorama = "*" eval_type_backport = "*" nest-asyncio = "*" pydantic = ">=2.0" +python-telegram-bot = {version = ">=21.3,<22.0", extras = ["all"], optional = true, markers = "extra == \"telegram\""} pyyaml = {version = "*", optional = true, markers = "extra == \"yaml\""} typing-extensions = "*" wrapt = "*" @@ -368,6 +526,55 @@ pyyaml = ">=5.3.1" requests = ">=2.23.0" rich = "*" +[[package]] +name = "cryptography" +version = "43.0.1" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-43.0.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:8385d98f6a3bf8bb2d65a73e17ed87a3ba84f6991c155691c51112075f9ffc5d"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27e613d7077ac613e399270253259d9d53872aaf657471473ebfc9a52935c062"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68aaecc4178e90719e95298515979814bda0cbada1256a4485414860bd7ab962"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:de41fd81a41e53267cb020bb3a7212861da53a7d39f863585d13ea11049cf277"}, + {file = "cryptography-43.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f98bf604c82c416bc829e490c700ca1553eafdf2912a91e23a79d97d9801372a"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:61ec41068b7b74268fa86e3e9e12b9f0c21fcf65434571dbb13d954bceb08042"}, + {file = "cryptography-43.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:014f58110f53237ace6a408b5beb6c427b64e084eb451ef25a28308270086494"}, + {file = "cryptography-43.0.1-cp37-abi3-win32.whl", hash = "sha256:2bd51274dcd59f09dd952afb696bf9c61a7a49dfc764c04dd33ef7a6b502a1e2"}, + {file = "cryptography-43.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:666ae11966643886c2987b3b721899d250855718d6d9ce41b521252a17985f4d"}, + {file = "cryptography-43.0.1-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac119bb76b9faa00f48128b7f5679e1d8d437365c5d26f1c2c3f0da4ce1b553d"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bbcce1a551e262dfbafb6e6252f1ae36a248e615ca44ba302df077a846a8806"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d4e9129985185a06d849aa6df265bdd5a74ca6e1b736a77959b498e0505b85"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d03a475165f3134f773d1388aeb19c2d25ba88b6a9733c5c590b9ff7bbfa2e0c"}, + {file = "cryptography-43.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:511f4273808ab590912a93ddb4e3914dfd8a388fed883361b02dea3791f292e1"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:80eda8b3e173f0f247f711eef62be51b599b5d425c429b5d4ca6a05e9e856baa"}, + {file = "cryptography-43.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38926c50cff6f533f8a2dae3d7f19541432610d114a70808f0926d5aaa7121e4"}, + {file = "cryptography-43.0.1-cp39-abi3-win32.whl", hash = "sha256:a575913fb06e05e6b4b814d7f7468c2c660e8bb16d8d5a1faf9b33ccc569dd47"}, + {file = "cryptography-43.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:d75601ad10b059ec832e78823b348bfa1a59f6b8d545db3a24fd44362a1564cb"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ea25acb556320250756e53f9e20a4177515f012c9eaea17eb7587a8c4d8ae034"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c1332724be35d23a854994ff0b66530119500b6053d0bd3363265f7e5e77288d"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:fba1007b3ef89946dbbb515aeeb41e30203b004f0b4b00e5e16078b518563289"}, + {file = "cryptography-43.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5b43d1ea6b378b54a1dc99dd8a2b5be47658fe9a7ce0a58ff0b55f4b43ef2b84"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:88cce104c36870d70c49c7c8fd22885875d950d9ee6ab54df2745f83ba0dc365"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:9d3cdb25fa98afdd3d0892d132b8d7139e2c087da1712041f6b762e4f807cc96"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e710bf40870f4db63c3d7d929aa9e09e4e7ee219e703f949ec4073b4294f6172"}, + {file = "cryptography-43.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7c05650fe8023c5ed0d46793d4b7d7e6cd9c04e68eabe5b0aeea836e37bdcec2"}, + {file = "cryptography-43.0.1.tar.gz", hash = "sha256:203e92a75716d8cfb491dc47c79e17d0d9207ccffcbcb35f598fbe463ae3444d"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "cryptography-vectors (==43.0.1)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + [[package]] name = "dill" version = "0.3.8" @@ -468,6 +675,32 @@ files = [ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, ] +[[package]] +name = "h2" +version = "4.1.0" +description = "HTTP/2 State-Machine based protocol implementation" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "h2-4.1.0-py3-none-any.whl", hash = "sha256:03a46bcf682256c95b5fd9e9a99c1323584c3eec6440d379b9903d709476bc6d"}, + {file = "h2-4.1.0.tar.gz", hash = "sha256:a83aca08fbe7aacb79fec788c9c0bac936343560ed9ec18b82a13a12c28d2abb"}, +] + +[package.dependencies] +hpack = ">=4.0,<5" +hyperframe = ">=6.0,<7" + +[[package]] +name = "hpack" +version = "4.0.0" +description = "Pure-Python HPACK header compression" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hpack-4.0.0-py3-none-any.whl", hash = "sha256:84a076fad3dc9a9f8063ccb8041ef100867b1878b25ef0ee63847a5d53818a6c"}, + {file = "hpack-4.0.0.tar.gz", hash = "sha256:fc41de0c63e687ebffde81187a948221294896f6bdc0ae2312708df339430095"}, +] + [[package]] name = "httpcore" version = "1.0.5" @@ -551,9 +784,11 @@ files = [ [package.dependencies] anyio = "*" certifi = "*" +h2 = {version = ">=3,<5", optional = true, markers = "extra == \"http2\""} httpcore = "==1.*" idna = "*" sniffio = "*" +socksio = {version = "==1.*", optional = true, markers = "extra == \"socks\""} [package.extras] brotli = ["brotli", "brotlicffi"] @@ -578,6 +813,17 @@ httpcore = ">=1.0.4" httpx = ">=0.23.1" wsproto = "*" +[[package]] +name = "hyperframe" +version = "6.0.1" +description = "HTTP/2 framing layer for Python" +optional = false +python-versions = ">=3.6.1" +files = [ + {file = "hyperframe-6.0.1-py3-none-any.whl", hash = "sha256:0ec6bafd80d8ad2195c4f03aacba3a8265e57bc4cff261e802bf39970ed02a15"}, + {file = "hyperframe-6.0.1.tar.gz", hash = "sha256:ae510046231dc8e9ecb1a6586f63d2347bf4c8905914aa84ba585ae85f28a914"}, +] + [[package]] name = "idna" version = "3.7" @@ -876,6 +1122,17 @@ files = [ {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, ] +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + [[package]] name = "pydantic" version = "2.7.0" @@ -1163,6 +1420,42 @@ text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] +[[package]] +name = "python-telegram-bot" +version = "21.5" +description = "We have made you a wrapper you can't refuse" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python_telegram_bot-21.5-py3-none-any.whl", hash = "sha256:1bbba653477ba164411622b717a0cfe1eb7843da016348e41df97f96c93f578e"}, + {file = "python_telegram_bot-21.5.tar.gz", hash = "sha256:2d679173072cce8d6b49aac2e438d49dbfc01c1a4ef5658828c2a65951ee830b"}, +] + +[package.dependencies] +aiolimiter = {version = ">=1.1.0,<1.2.0", optional = true, markers = "extra == \"all\""} +apscheduler = {version = ">=3.10.4,<3.11.0", optional = true, markers = "extra == \"all\""} +cachetools = {version = ">=5.3.3,<5.6.0", optional = true, markers = "extra == \"all\""} +cffi = {version = ">=1.17.0rc1", optional = true, markers = "python_version > \"3.12\" and extra == \"all\""} +cryptography = {version = ">=39.0.1", optional = true, markers = "extra == \"all\""} +httpx = [ + {version = ">=0.27,<1.0"}, + {version = "*", extras = ["http2"], optional = true, markers = "extra == \"all\""}, + {version = "*", extras = ["socks"], optional = true, markers = "extra == \"all\""}, +] +pytz = {version = ">=2018.6", optional = true, markers = "extra == \"all\""} +tornado = {version = ">=6.4,<7.0", optional = true, markers = "extra == \"all\""} + +[package.extras] +all = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.6.0)", "cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)", "httpx[http2]", "httpx[socks]", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +callback-data = ["cachetools (>=5.3.3,<5.6.0)"] +ext = ["aiolimiter (>=1.1.0,<1.2.0)", "apscheduler (>=3.10.4,<3.11.0)", "cachetools (>=5.3.3,<5.6.0)", "pytz (>=2018.6)", "tornado (>=6.4,<7.0)"] +http2 = ["httpx[http2]"] +job-queue = ["apscheduler (>=3.10.4,<3.11.0)", "pytz (>=2018.6)"] +passport = ["cffi (>=1.17.0rc1)", "cryptography (>=39.0.1)"] +rate-limiter = ["aiolimiter (>=1.1.0,<1.2.0)"] +socks = ["httpx[socks]"] +webhooks = ["tornado (>=6.4,<7.0)"] + [[package]] name = "pytz" version = "2024.1" @@ -1307,6 +1600,17 @@ files = [ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, ] +[[package]] +name = "socksio" +version = "1.0.0" +description = "Sans-I/O implementation of SOCKS4, SOCKS4A, and SOCKS5." +optional = false +python-versions = ">=3.6" +files = [ + {file = "socksio-1.0.0-py3-none-any.whl", hash = "sha256:95dc1f15f9b34e8d7b16f06d74b8ccf48f609af32ab33c608d08761c5dcbb1f3"}, + {file = "socksio-1.0.0.tar.gz", hash = "sha256:f88beb3da5b5c38b9890469de67d0cb0f9d494b78b106ca1845f96c10b91c4ac"}, +] + [[package]] name = "sphinx" version = "7.1.2" @@ -1515,6 +1819,26 @@ files = [ {file = "tomlkit-0.12.5.tar.gz", hash = "sha256:eef34fba39834d4d6b73c9ba7f3e4d1c417a4e56f89a7e96e090dd0d24b8fb3c"}, ] +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + [[package]] name = "typer" version = "0.9.4" @@ -1558,6 +1882,35 @@ files = [ {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, ] +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "tzlocal" +version = "5.2" +description = "tzinfo object for the local timezone" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tzlocal-5.2-py3-none-any.whl", hash = "sha256:49816ef2fe65ea8ac19d19aa7a1ae0551c834303d5014c6d5a62e4cbda8047b8"}, + {file = "tzlocal-5.2.tar.gz", hash = "sha256:8d399205578f1a9342816409cc1e46a93ebd5755e39ea2d85334bea911bf0e6e"}, +] + +[package.dependencies] +"backports.zoneinfo" = {version = "*", markers = "python_version < \"3.9\""} +tzdata = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +devenv = ["check-manifest", "pytest (>=4.3)", "pytest-cov", "pytest-mock (>=3.3)", "zest.releaser"] + [[package]] name = "urllib3" version = "2.2.1" @@ -1928,4 +2281,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8.1" -content-hash = "7095f314c771bc5604cd35b3be6c42780f46c0be413d65296e4e0aed5a4aa771" +content-hash = "c74907728cefa15f4c599238fa17b5f174afa6f54e483e75e9a4388265462f51" diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 983e5ee..0b11f46 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -20,7 +20,7 @@ typer = "^0.9.0" pydantic-settings = "^2.2.1" aiofiles = "^23.2.1" cookiecutter = "^2.6.0" -chatsky = {version = "1.0.0rc1", extras = ["yaml"]} +chatsky = {version = "1.0.0rc1", extras = ["yaml", "telegram"]} omegaconf = "^2.3.0" pytest = "^8.1.1" pytest-asyncio = "^0.23.6" From 1de852f297e31d56bb06cfc156b9ffba99faa0c1 Mon Sep 17 00:00:00 2001 From: Maks <90211175+MXerFix@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:47:56 +0300 Subject: [PATCH 3/4] Feat/integrate slots front (#92) * refactor: init new base input elements * chore: configuration changes * feat: slots implement + modals rework init * chore: icons fixes + modals rework fixes + sidebar&utils improvements * fix: slots filling and saving fixes * chore: add slot condition settings & rework condition modal & add shadcn/ui * chore: add quiet save handlers * chore: slots components refactor & add condition icons --- backend/chatsky_ui/services/json_converter.py | 12 +- frontend/bun.lockb | Bin 457390 -> 491250 bytes frontend/components.json | 20 + frontend/index.html | 1 + frontend/package.json | 16 +- frontend/src/App.tsx | 10 +- frontend/src/UI/Input/DefCombobox.tsx | 132 ++++++ frontend/src/UI/Input/DefInput.tsx | 52 +++ frontend/src/UI/Input/DefSelect.tsx | 101 ++++ frontend/src/UI/Input/DefTextarea.tsx | 32 ++ frontend/src/UI/Table/DefTable.tsx | 39 ++ frontend/src/api/flows.ts | 9 +- frontend/src/components/ModalComponent.tsx | 2 +- frontend/src/components/chat/Chat.tsx | 4 +- frontend/src/components/home/FlowCard.tsx | 5 +- frontend/src/components/nodes/DefaultNode.tsx | 39 +- frontend/src/components/nodes/LinkNode.tsx | 5 +- frontend/src/components/nodes/SlotsNode.tsx | 91 ++++ .../components/nodes/conditions/Condition.tsx | 33 +- .../src/components/nodes/slots/SlotsGroup.tsx | 159 +++++++ .../nodes/slots/SlotsGroupsTable.tsx | 133 ++++++ frontend/src/components/sidebar/DragList.tsx | 17 +- frontend/src/consts.tsx | 4 +- frontend/src/contexts/flowContext.tsx | 54 ++- frontend/src/contexts/index.tsx | 5 +- frontend/src/contexts/popUpContext.tsx | 62 ++- frontend/src/contexts/workspaceContext.tsx | 2 + frontend/src/icons/DocumentationIcon.tsx | 23 + frontend/src/icons/NewWindowIcon.tsx | 27 ++ frontend/src/icons/SearchIcon.tsx | 27 ++ frontend/src/icons/TrashIcon.tsx | 23 + .../nodes/conditions/ButtonConditionIcon.tsx | 6 +- .../nodes/conditions/CustomConditionIcon.tsx | 8 +- .../nodes/conditions/SlotsConditionIcon.tsx | 8 +- .../src/icons/nodes/responses/BotIcon.tsx | 42 +- frontend/src/index.css | 75 ++- frontend/src/modals/AlertModal.tsx | 58 +++ .../modals/ConditionModal/ConditionModal.tsx | 430 ++++++++++-------- .../components/PythonCondition.tsx | 98 ++-- .../components/SlotCondition.tsx | 115 +++++ .../components/UsingLLMCondition.tsx | 7 +- frontend/src/modals/ModalComponents.tsx | 96 ++++ frontend/src/modals/SlotsModals/SlotModal.tsx | 83 ++++ .../modals/SlotsModals/SlotsGroupModal.tsx | 232 ++++++++++ .../src/modals/SlotsModals/SlotsNodeModal.tsx | 107 +++++ .../SlotsModals/components/GroupRowItem.tsx | 54 +++ .../SlotsModals/components/SlotItem.tsx | 82 ++++ frontend/src/pages/Flow.tsx | 16 +- frontend/src/types/ConditionTypes.ts | 4 +- frontend/src/types/FlowTypes.ts | 18 + frontend/src/types/NodeTypes.ts | 13 +- frontend/src/utils.ts | 137 ++++-- frontend/tailwind.config.js | 27 +- frontend/tsconfig.app.json | 12 + frontend/tsconfig.json | 7 +- frontend/tsconfig.node.json | 2 +- frontend/vite.config.ts | 16 +- 57 files changed, 2467 insertions(+), 425 deletions(-) create mode 100644 frontend/components.json create mode 100644 frontend/src/UI/Input/DefCombobox.tsx create mode 100644 frontend/src/UI/Input/DefInput.tsx create mode 100644 frontend/src/UI/Input/DefSelect.tsx create mode 100644 frontend/src/UI/Input/DefTextarea.tsx create mode 100644 frontend/src/UI/Table/DefTable.tsx create mode 100644 frontend/src/components/nodes/SlotsNode.tsx create mode 100644 frontend/src/components/nodes/slots/SlotsGroup.tsx create mode 100644 frontend/src/components/nodes/slots/SlotsGroupsTable.tsx create mode 100644 frontend/src/icons/DocumentationIcon.tsx create mode 100644 frontend/src/icons/NewWindowIcon.tsx create mode 100644 frontend/src/icons/SearchIcon.tsx create mode 100644 frontend/src/icons/TrashIcon.tsx create mode 100644 frontend/src/modals/AlertModal.tsx create mode 100644 frontend/src/modals/ConditionModal/components/SlotCondition.tsx create mode 100644 frontend/src/modals/ModalComponents.tsx create mode 100644 frontend/src/modals/SlotsModals/SlotModal.tsx create mode 100644 frontend/src/modals/SlotsModals/SlotsGroupModal.tsx create mode 100644 frontend/src/modals/SlotsModals/SlotsNodeModal.tsx create mode 100644 frontend/src/modals/SlotsModals/components/GroupRowItem.tsx create mode 100644 frontend/src/modals/SlotsModals/components/SlotItem.tsx create mode 100644 frontend/tsconfig.app.json diff --git a/backend/chatsky_ui/services/json_converter.py b/backend/chatsky_ui/services/json_converter.py index 4c52222..ba35e61 100644 --- a/backend/chatsky_ui/services/json_converter.py +++ b/backend/chatsky_ui/services/json_converter.py @@ -11,14 +11,18 @@ from omegaconf.dictconfig import DictConfig -from chatsky_ui.core.logger_config import get_logger from chatsky_ui.core.config import settings +from chatsky_ui.core.logger_config import get_logger from chatsky_ui.db.base import read_conf, write_conf from chatsky_ui.services.condition_finder import ServiceReplacer - logger = get_logger(__name__) +PRE_TRANSITIONS_PROCESSING = "PRE_TRANSITIONS_PROCESSING" + + +PRE_TRANSITION = "PRE_TRANSITION" + PRE_TRANSITION = "PRE_TRANSITION" @@ -147,7 +151,7 @@ def _get_slot(slots, id_): def _fill_nodes_into_script(nodes: dict, script: dict) -> None: """Fill nodes into chatsky script dictunary.""" for _, node in nodes.items(): - if node["info"].type == "link_node": + if node["info"].type in ["link_node", "slots_node"]: continue if node["flow"] not in script["script"]: script["script"][node["flow"]] = {} @@ -171,7 +175,7 @@ async def update_responses_lines(nodes: dict) -> Tuple[dict, List[str]]: """ responses_list = [] for node in nodes.values(): - if node["info"].type == "link_node": + if node["info"].type in ["link_node", "slots_node"]: continue response = node["info"].data.response logger.debug("response type: %s", response.type) diff --git a/frontend/bun.lockb b/frontend/bun.lockb index 5dda435e2fb2e60cb0c0b8457f70ffeb570cba52..d90b23ad26fd3ea8bfd6418ea9fdc8d8e0c7e51a 100755 GIT binary patch delta 121378 zcmeFacU%?czCAp%WhcddDzO^Ba!&b{~CbARtY&d1IAuJ!cqQ)l*oYo8Z>^1kS_$`xkb zcl~bcn!q80mBTNxs=GQ|`uX12idX);aeT)66|3fXWbZvPi^8kL^iIX&7TH})q9}c$ zBO?ZeL`GD<_dv0KH zS4GJN92gT8+8gP{f`10?4$J|3gLrx4U>rhfX0Ti04Fu!|_XjeA(!jF7#vmntf1sc| zzOiK82F5XBZg)i~1*`;tHSY#w z1!7}EgHds1V2o!(WN?V`2kflS5!b9(6mq2}hDI=!qWqFmRBShp@q$D9^r5~1oGlNG z?n|nylHq|tk@cdPHUR=%GC|@HiTxzD02W2~wY;LGJ<%+-#4n#-O)VQY0hKlD;pJ`C zqSXq{J!lkr3FWQBaw13A%_l8^{cT zLOnwx`a~*|P-AlU&qYQtVSzFI6h*OuvqjmAW0M;+orqN5|DJwt}5WktH*q0?i3 zt2oE7v*yPUkz>>8fsb7_1>x-JwdKW-ds|u*+&eljC?rNvR#p%^4fq-2)$e@U78)KpP>HD|A~r?1DY~p&Wsy+`5)TZC z9(n{i!*4^U2P5PdiL4?#wg(krR~JS;^n{}-{Lg}%E$>`a)Uc8)=(v~2=qDf(#)O0q z3W-*frqzW$G%zeIG-jakqM8_lH-VT}@t1(Cz~maj6CpzegmC>?EyH&J(d+Tc^Xpw* zD#x|+7ReSsX8nuR5j+!+Q>8J&ImY{jMfMI1i&4shvpV5EVsZHb$R4i=Wc->kejaeH z5gm~)JI)bWsSM(ecPDDiFnC@aMUIj9PDm_HYG>k9y z$Zk}W9q|C^ICi`B6cyS8JsY@7FHz20=&Z;eK=(Kp!6CsI8Hy4V$$n9kHAuh+4=kc( zKL?5mlt90+Yo-A60XINr!L5S?_XaXN0qHquRJe}~Sr;rSFra^5&*0E#C1ePeOk8mY z7t3gLWcYxAG0LqFQDFbDkl1KMc!}KT+D(1L4!jJ=0z-y`1fgu@3U+qxOpy^G14F|@ zlyEG@L7sggPC;kE0|E#3^Xwlo^cQe8CjjRej|$I=EkG5O<`|7-S7B;t+lP z5Xb~GQLsDkaDU;_9YA)$G$1Pw9@syGO$!MV1uuh+#>G1aa~wFka2OiIG13ai3T1YI z|LLli7%+5Q2)cj~f?~vq7Zw?8?v+n7iGr@7a@i0b6B`sA7!#tbi4y6j?@Ww{7akWH5jr3+CdRoiemr|OAR=q(h!rjF4rE5oQ_9Du z9p@>?dD>|=STxAFH7`Ye^qh0qid%*jumBs7EgujWGZ3v;zQmklK@ouwkvtH7j&Qo# zc~E{eK%^@Koqc%?9$@_JGM|aVg+738z!eK_4oZa6Lo$6_yd!jAC?kZ%c$(g+JwhZ* zLjjCSkE&{mJegkBU`$k6g=4OEI7&vr5 zh%yK|ThwB_D9|CfBPKX994*Q*QB2Rl=wn`)!PzxuCkTCDbd6fn2gF7U4Z_j>CGNg_6I3Nq!2b~q$1|;wIt?2XZK(>4hke>Jwm>oDECXD$jYZ0Ga7ZW&y$2(<6 zco@!((J{-UZf+Pg6$Pta9C|+_CSq_*-~fz*uMwdH5=H}A@YAWHLcPK1vH&2L*v3E& zSY)~g$HIg8-&=75XG5+6S+T&Fp%Fp-LkHGDe0su5rh`Y}(y)+jndIPb=%Rq0K-OHD zDH8lM7dC*q8WIybFcc@ad~-y_ z7D1=Wy(Z{0vX_mU0m>d}C*}6@#3ZPWdgg-u2AuUM3(j~m=8O721=6vFQyB#D#GL9v&n#$3q{0hKsxBC8e$GLRV$mEnDX ztY|yQeSu8x0c5?qM{Rl?4oOk$F7T9EA-&%p2H$B?>n!Lf$Rd0oq|_Ego5DF z@`P7nmk197GQ-?Jw#-@38Kk4`ywlz%^T~;Pia}R^Ecll_qH8t->4~`SV6bIVfUMD3 zRp8Q}M9aqki$D(p(nIZlte7v5@we|2V`V9j2d7CAV}bNQPaxxY9uN(i2xNIffT&Ph zyeBeZ-|Rxcoac!^CL97}!5t5Ynyv?uJ1gL<(0{igeUTqM;B!orGy4cD(io@jW&$O^jw>GD5M=-YD_iz{_n^iTwl(VWjfoU?NcilN6N z&xm+GqaMuO8J-5tNt6Gqm_!LOewTB?A5DP_uL7i>9H?Gl#s`Pvr~nz>`gc|Y z$DuQb$Ox`iM9Y%E8DR-HD^>wK7qI$S+P9VMYsnzJ(fr6Dv%zi zhx8mF6K@Fa37#tsLeZO|;QX?nEI=0QbxU~U>t98J65w>b8;}`xx-BaH2Iu6tV`$hyUzuYO z>(?LBLqN80jl^~Lh2AVp6Vz5O4*??X^#`8c0SwN#FvI~JsR|QDj@2QCI4P-v&AH)C5;J{;%K@yOIY7USI zUOW>STnCanXMqzZ$aKSAh;$AhGj1g-_Vr7#90dY7XnkcxH@y;kUK|V-IR3R5#V>)3 zSV>0Q`bN0405~h~3@ywF-1}Cvu--c{o5upV%v1rg0&5V?L1_TlC0C_qs(ui>2$1>3 z9Y+8Yq{zPf9-JPT@~6P7K)P@&bmCw|HOIt2a8{(Ds+#dD1DRn_AoU&yX9c=jRI`Dt zz*&wD~dU%Y`RiEL@oX&K1qM(m7W$=K?Yg3F#5%7U0|(1_t)-9oRc8q#6oh z#lFm@ntNLza8|r{cGc`k=M%bp;2zMO&mzrdn4JI4r^L>Om=!)#m1@x4fb7#dZmPN4 z?*LW;Ux|#kIle@J<$4*d$*(|q zDiO#QPXf}TVN&-8(lh0N>>^iSap1kCM9hEZS@RcDz;pz%ARWk-jsv;@KYmd3Z{9lo z{mzl||Nrf+Bc5_B5KH@hcsY(O48eD|d>-4klbC+pCFbj_nrk+`(2HP~WC!Q;yeGq- z0(m|N4#Csy&={o+cy{ngK#n2jqjTpY^J36HgC5#9A~G7CoVOF^zXrkib&d1uDCe{E zkDsAikpVZ6V_%Aj;DLN}U>`-nBiBK!;Lz@3fx)+n_?jzNdC^fUXdy9CvG~?Y;WPXY z6nq0ZH?vheFuGZRa}Zc_y{F*E%j(056^onPOYAYb5zHliDUf4rJdlnn)LRUd5#V%8 zIFR*w2c6kXYA+7WenDagwMRG|Rtv~{l2B*zC3G(En-Edocpw(axcJEk$b*3Y>7@90 z68z)A|6h%LOpgD^AV&lKtufBY@$Wq!`D>8>&_}rbTOilBK0vNZ81AO)BL<4)pc;_7 zc5@(?kAde4G zK<;t=h{trbfgCHIK$e5G$<&>%8(^c=GnLF6w|1*iQy({e5<-e^#*;@wq~+|QoPJfQ8375!)S zUe__l>vdnb-S#g%_Ug#EUImj+`HpLSFgc-R=cbD;Coavu6<>jY7 zkIpjwRsBnaHXX`5{9v2+!$({?);G_A#Ae;DDPs?hn^`kCYqsj8B5KVjGkfzH{|22_ zw}}dB;{Ia%xz)vQq}2HS&6~3Qo<|L9s_0j}O5_L}^HYnJ(LM7m{4DImgyuVDY^+~$ zh-KsM(#JDb9Uaquddj@zyMA6fxM41}@$jY9&$e2${I#u}--RbGBWupteSMJcg4(T4 zM_Grq2z-2YpU@9qm@qXK(ST zdAkR0+dj9ne$b=*hKj2!`g+fUad}%_xw^ml@=#~pKW$uGC z=k(XdzF7KQ@PPe0hUB^wyR7k_2WyUTHgDyop!z?3_Uq~~w{w)3l_jU;Bi}R_?`*%* zNzRUNI;-99-EMTd9G@+r_4=%%XKt!hpqpjSZy%mHochN2%JtP{$E&F2-P=zo@3(gD z=ScwrCqAlq;Bu8oO@6g?m^|jrq{3gSJ(wcbQ+dNU}#)mnl289LayF-H)D68`N6x(}dkF>i(;-ea<@*J2OKj>r0YVC2__4S%To$kH6x3O34FI;m>uhMmL*-gH+waB{rQ`^klp|5W+ z#8Svb_h@K$YwjYxg0WbO{VMVb-^-5+qK^jRu^HJ*--8CLV{Vzti8ZG>S+^ww9T+Mq+HF8 zdIrYMz>q?N?1lBj=5}oijvYKDXVSekd%J>g&>=o@eF;_vEDKXx>K4%xTiCVnILWdP z(f7#YR1w{yrQPa|eW;Nh-o37iX?TvSs}|K$TiPujis~K#c1zu2x&tt&n4SnYTue_5 zuv_2bz=A}h>$>9f%u-$G4J{swZqLH`r2P!W>WNwO4vaO*tS7gyS!i6ENe^X ziLLG0I|!T?dG*IlY+B=ziqaY^lb#Hg1lGZ12(wzS>2=XP)9Sjw@S)SSL|Dul4Qpw$ z+$^O#+So0nOY4bk?A9(g@7H52YpKsM*!84FKGp%Ss+nG13X6SV)stI!t7Y`mwsvdZ zG8nSD=ghjUFxUW9_iAFZWG|~HwzFHBm(^1N3(M*r?d_JEWpzh;yR|&-cD&6Dd%@!T z!t&4@D*+fg5$$MavpguLJ3834s^vwQm^_Vbmay`AY6rXJczNB?(XQDlh%k&|B#!~B zVJ44iY}1lu82S`F{1%M9Hph+T=aHemt)s!}>7H?Q)rz`DXS?O6in^n-UHh|Q##PF< zlA_cyGxyr#?Fzz*DxA>PAHhtYX<2Y0VJWU0cUlXu`e5ebVf_ZIf$9HSuo^sKb+MK%!(Bf}3&ROkXM4hVDTS#x8AVzgppSJypy*tOPJ^Tkl) z8aNS*elX)&c2(C?d)Td3xIMtELR(noD6`Ik)r4c#dKFepGc<2aMQLU>;Y(Qkkd0Wb zC)@PIUUn@S!v}FW)v?;I2jgV1=wALd%XM$v5oos*udOEr+O;+~0>bME<3t((*3x9C zq4f}09X+X+k5(1c{!9M~th1ih!N+=svn5$Zoxg4Xdsx6sfDH24Viz)jfjk zmfLl8N3h*$_m%OjBVmQ;;T?S}H+*%E5WA&`U3UNm+x5f{yS5*Lg6oc=CkNWJGWDDr z3o2>t4`w&ZSY1y~>|?jqM-O+_J%fEL3H5c4zIN+V2*J8%h>xaYF{I0_MpRSP+Xbu^ zXQGw~!v=&SC)j40*-&@%!&(55lM=4P;^o~)Pwi*73~!`+gklwLq&q_G);t)LJ@mA8 zKGtYhO=)RcX(1WbNKCd5jdh2^ZXJ!`(b25r`6hZQgeHhGSWk2KSbu~SNy}2PnV#6+ zZk^LiQAU|Tg|HNj(Ubc7SQo+yGBwqo%K|h@kiVW9X15-P(8)|+uDR|Aw_9hxQ|9=y zzJ}FY4{z*a>C!^?h_Gu$3+Mdu!az6!CWc({7dC4)^k_44T(yOT5f)e16$V!xjPiyy z?GG@HBKX74W~mUMr$*Yf-~h4TiG^k+80RmB&v9>88Dwr2T6Rn;YCs5670b@(VpR z+OB18lU@!YHv~g}h#9So1H;7S#^7($PJqF!VyajRqJ*mEvCAKpXeXDu5n#dx+ygdA zhUR`@)6&4$TyunI72Ao5p@m4-AB?9FEa?~qXWQwiv39L|d(jdkK)Ysxu_ahLF~DxN z*AoZXH9u@Iavi|#II@H8G1zY11F^22*2G794~vz?DR7CmD+-Wtk==AK)<$e}$0Rc| z*W5aZR$zc583>c1V_Mj>;b7HF#@#Xr%$y6U)(uQ9l3i`uVlX-xLkrz>4Ggo5-GpVQ zbhq>)UyQd4h}d3N!IJ&e(q?@GhSe0yU|o!qI(izcL9oP%y$hCDF5kd1S9fc@?$Xi{ zU@@nx<{{t_7`7QK19f_&d(h8j9Re1h<0Rq&qn)|V7U;=Bxr>Lw!jMIwNwCB&<8@*){h-vC?8I!hG-ntAiS$@6fhbFwu@Pu(0!>7+Y^n zb6A$|dh3a!?bas{n(9fTeJmkCy2sac%Z4D`@wMHm1~Uok7zqn^5=guq7WOTi=U%}Q zLx#OwIYdkdjHYJZE+FEFvJO@)-Lt)q_7GNGvtxJ=sEEGhP{FLlKGz-0riZtz>jI;j zSz?90a=+3B!D3VlfAsGYFwAlGHnzI@{X`wCW^WGy6B7*H(bj@-PQVd3hdyAKxk8r> z6%G>Pr56~-3mDQY1!E;M>s~S5t{|KRIG$j>HgPxyH|O%;bjE6|29w=_>G#f|r%uF{ z)ZaP4FTiSyj5PC17N#dovTIRcq8*v^s9oN!Ags8UZ!f`EaV&i($rg@1NQ9yJQ^7DD zQ9*{?0%OLCxe8T?5DYW(nztIECw^rJm~m=Lv>W?prYW0w?o^IO0X=sfX;A-BG&7FLo_wv~#C_j49U(j4^S>#Ar+e z;~+slW1F~|9>(ROSd8|-x1DZ^rQkE3?&M=83Rz6%^AqPQB#4RId_#*Rl{!%ycV z!!~i!+jX#WZRLs*2*&22)L5Ig2uyYWlHHJDNQRr=l0(GOfTMC>Z&wf|Lk}b3N-!}; zIi##h?s3Adg$_+$$IyapL-oWHc552MR@{X(pJ8IUVc7P-!37NGDXcro zVX+t=OvLz}RdtTvNc99D@ik^N)0Hju=u}3oynMGkGGI z7zN22Y?ezS^~AGw>t}IXOOl%SSR2E_O1#rYn+1z=1)~jqy$Qxa0OoJAdc<=TN^0(- zg~FW?vN3^3Vpbm1v5Z-k*z7_S9KWn^u@*fU~rSOJD(5E#pP1jam3VVpaPjpk(c z!~)q9R%1OJN5o~Y8k+GrSCy}^5`($x$xUsVFBn~sLx0@WW*Pjoo;byB{SjhoJ*}mW zrQ{ghW2)Wq5y~Zr3(KWP3g{qdbyP%-eIV?j$jq#EBscj2?y;u?Bnx#y&FFI%_JK zIdbcKBdUcB1m2wiR^Lp+Np}*gIhe?=$hRU4>s*Al`mLTi%dQ=O#%74)f;QRNZ|tc6 zFy@6-5^OS<7z@d5Y}yVm4m+$*ctZ6WtQwekAk->Oan_sZg1{IPxnP_u0jmL)#oTz# z$e5^6dz-~=s-Bo&*Z3|dHXT+8lW#Q`n;=%Q_h9S;Foe~fCRPeK?;CGdkhTaCd%-C% zjuVqv%1+lEbM2O(>3ZT^yEb>abF||WtvvwaSQQ?qHbd0iJR@5iGxWrH*bE>tGvUT- zGKyF#3hE*=b3@jKf%zdO?vh*BEPHi5b-rE8I#W~{{fItkDw)V^G+0X{K=K4{SCFb8 zC>)#n2e1y@)3u(n#Hh++uGl|-F^OoXGFuF%Y#dHjUoae@C;C{!Vc~8F=Z%fD5Ff>3 z!Fmlwe~Q$NjPwBle@+IYcg3o62#jqJ6)lw@$}w}X_5^Ed=KdQj`UW~WyTlwZb-=Jv zbp&I6IDuf>ngPb)BuYLB#&p=nG0C#eWxd09*L8&<#ylN3aIWsL49^T8RzqA2@%A?D zJQ#;8dVP?$>pYPST!RMm1#4y&fhlTT3)YgAv}p77#P4vsKObjD1ZU%|gJsox-DA1k zl4*hN00b=1Qvp*J=pHNV)~SoQ7n zutbVouv(gFvn_RM?O?SswI#4bXc{b;%Q9!Y;jlz$yVA8R-#JsXgC$bTfz{m1?*=SU zZpG!!c!Ocdy1+8?(=x3PsNCE;RqD^k>rOmQi3_Z^ewh0@lkJ(^_veZ>D))wFFk<^jNpR{wmcc z5s#Uxmof+K|m+PK(?4L@*JB8^P_(y5k4CwapeRd*<_iF|d4)A0p#` zxC^X-$+*8LTSZ4;??G9O!2-;Zqo&%d)4`&2&ka7BWt%veVQIqytF~aF)U79AwK1(y z+xdKl+E`c(c_7dZz~aF9Opgk&X%%*e1uUz%)GY;Tgs{T8*BabWfYk=eX`YeG|0tY+ z354eK{!#bXX4f`BWE;h5_!z7Pm{=*xq=?KAhJNb@#<7JX57wzQDY|33-SRL+PXyH2 zsiy+I+Npc&uv@R}ls6yNO1tEU(gwj|IXKKM_I3qn2qG5UXJBo?#95)~?(~RjY}V0W z;!*eyu;^c;!9C0K-MS;iZfUzmPfW2}FYjS7NhvBjT0LQ5Nr1Hg7EUE8KH3df zTn@x>cF#}ZVGfPq!uElEA=puG*L}`4g&lnmtWJ7F%l`Cr0AXMJ zjV%P@NH?>z+}^J{_SaJn=!xdfRP(3DfqJe7#UK?0cK{P>M>1B0C14^4ZbQl;J$0wu z>NsS!?h7C72rRKUdEqOjU(&0L^)VU@J0n)ApJCAj$RD%sv%~4NNB(|bf30G((^*s} zZx;|hW?-#!#Pn-|kJk2Bx;OFtL$l+0>Os49_PChAI94~cS*o7U9k{g|enR)Sh&xQM zi*~L4Nzp{}e5H*9Ym5qs=N)^&*hGB6fC^ z%pADZM_|=-Pk$dv<>`Iu(s??XPDJi?IK)kKH3bv zs3%^tYc7|Z9l-^@)g|5II-W#d(j9=hzv+p98NcbN*X>&BZ(>H{d=p}`cwW{WH|*B1 z%iNBWFoL$yLiI5};24+R#j=gA#M0~lYiEX$rN}VUF9r8mSDeee*C-sYz*-0=T(en| z!SIL@O-s9?C*HDa5m&{?L|0+q-Fa2_xNWyaU*orCX$O6*hhh2CvQ)mVr{2b*a$Wbh zW4G?Q&bvgzuieA_8H0!45Z}i)BJnp!u zr{1+&l5XlA_w3f@w=m|}cuT@9J@KC1a_N?yde5#ExSihDNE3NmcckJmX1W;F*`~b$ z^EC_MU0?G%y2pLHW%?c60eEvqPXyGttEW=D%O+^2?}`zC9sdwkhkNPIvN02PfpK{= zM~+oV<$PtW+NSD`G`qGBLNJ1n6gx$Q`)ISCcGt%mW?JFXeJp$K>y8I@Ezcj$mPd84 zX`x`!Ie2in2MjK}>to5BrYAnMTe_y{sSoYed1>YiII_D6%U2J7;G->lAQlrb6kdUG z0Tj&dp`vs*>uL@Ku+AoXjK%Sh(vV{9_3$e`)}yd|^rU+}7T3pS zFZctV*sZCLSp(==*C)<_ABBe%KY-z;e?eUr7>F>)$J+HN9Hl2+_0i_R;+7(Aa?XK? z$;pQ2d*-YM?*!X{asR~_`Pyb#{7iQ|#|h{;^JcC5VBz6)BOlAe=eoxW>}t<-2cY5$ zJ@JKIoB2XC9>WK-;wG3V)oZ!UQsSlVcxl&`zEqU1D8TfO^&MC*v-iGyWj62JD=AoQ zuaTEI*OtL*?@Xcn|c;b53M>`6usWX-}_gi`){JxL1Gb~j8zK?bRR!78e z)j9Y4--+6Z#cT;!FN9&o$B9vWpJDsZ{K0rvf&urN%{q-t4^Q>6o`Z#_F(|vq2Uund zE*6tM=#F=G>oJJFW)EinlRan+_|xnUblqZDV$$)wlBd+o)L64J)lgLZ{(E0-8eYlg z79-mEI~cn6o{zP?N()2Ns>8w#e$~f%#?)x#v8Y_}Ff0edN*{>U17IAkb@`?~p0!~4 z2!n-3tYkPkEYEl{1HpIR(b8 z2a&>U*8G`OeyjQfO@_s>Z$8}3^eVGzRLq3Lep$>Hh9N(mZ*yA*W{y4uzyF{=zHYwh zry7-~y3Q-KyWb82Kz#5vRCJ(i_71U67G9)O7qkKi9L`hY1feLrs_i>vG ztBwrUaKsZY6rFaa(xa~1+;V4C%}zlUN+CVDmx$9IDs#pzx51|AU>q2z{047V5N{Bi znQ@@Wg}3;5c)+Q4Cl(hl&IBCkez94XgPGq2WonQeZg5e3tq-%SN=L)fMfI^Z!S4eM z5@t92sz8J=55cTsnwc`W%S55>aH)}2Sgq>Jf>kUj#tqJ+*oC6Nf<#zw|vSe5eW-Q6A!slmcbznz06{M(^|stgdGh7~3{$IC-i2Abk>SPs*2ugDJL z_lDuHhMCqKSP`bxp^~cfqotjM)y}M3@-&;ZjHffFIk3iw?2A`c@hWsUzAs+{%Pc*U zdlfmBVyhs9h<6XBsAb=(&T5>1B?o0QFK6C6U`;UV(WsiL^f9f~uqK%Ur+Rf+H|soD zVyL;*fM$djz=(x4&P?$Z)+Ey!TT|ut@@VTFSPmm8AL7+=Ixhv5@I+x72Pfkhuwu>c z^s{@janyc>rE?HiJJojjX&`cfuTC`mnOEvr&s-$sWvZce-v8tU*Rn4wQpmt%&4%8P@f%I-6NqT zcYReEW26;A*0*7ronq%TTIF}bT7rJgg4vLlQ$k)#p%;N(Y~F=I zSKS-0(2vh7=+a14=9`Uq3u~ci&1tMEKblsDCaN;Stc~^rrWX?92BD43(x$1AS`@{1 z^HY^>Mp{wSZ8OX+!gTjfR~<0h2{i>~cVQN4t}3Cz{2FGUFmJ+?5gN2WX9#r_%n)I^ zwZxPcX4qfmVVE*m`GE8Y<6+9`T!q=mOs3gdsm_NCc*qgm%5apz!n7JHcLwu|66<|1 zo1V5D?~t{|8fIF3VBtwCv^B7B>jvvtdZ_moPAwjmh_@e>NSn2dGqf!%Jgh|8h3VRD zSglO0Mq6iSJS>?DEK%BL?VS0wg(bAPuta{hVTlx#+GA5O^BV+9#M=o=#LL>jskMb= z)=!%as|Q+-hxa>d7PX_{Q2{=Q>Lk`D@x?$=I>T|$TDCKD;Vn)Ctg0d{9?UER;}&K< zSl2E}CJs;ex`@pV>|1;h3RcUEnLNwe6{J20?ozQYoCS*p!!0#$qPvQSxM#(CYarY{ zu__~IGgxCVabSG|Rs#%Avhme^+$ z@mQO6K3E++sfCYr78Vb4=3^jh*Zlb2J&OtI6;^lEo{;NM)S4A@CSfY^peSUj<`V z!OJJ`J$WC|IbgW6KLN%bfTK_wwXdqwG-E~u*|b_<-O|}~urBE=^>1NK`iXrKPgIaD z0W2^*=4-I->8xWY)~0l}1B@pUk*<&KN zXqc)rF_P+Fq3sQ`nJ`zw^cUunzarSe)75yG&5UqgY%#E82H7Lh!#ltX5E(4}OT7=X zwNUFsrbieLQ<$PGGC%<2ZnX6rH?il z7T2hJ{Jz6&9~f^Bc?iz!yeZN$4-;z@X1tD<^uTH(PEkD>6&Mc2#-iz%j@yPAiA^!p z-a+LWf%nR1;d$e5!=ss6C(a+a^23YBdnP>CG7;5Qc>Km%Vj`9Z^CD8mFd58=ZrBf5c~#fP4_gim&S?%=}~<0+JRWkk04F+R-qi5Uw2JB|Mi zGK1&%5MSZLjPPv6#5X`*M0N>9#>>P=A0Z_dp~pFmzad+k84(yKi-`C)WC3n6{4*I& zr0Fg(ht!FTpHK3?Bm7UH03uYD5i%l2b2X`FL`v1APGp5^0@-43AcJc2$45x1jtuu9 zVgJ)$LOTVC%&@+Uh}!@&LmcrciKQRB^c$)VewxN# z$hbH0gLn%+=P;jzl{%3o|AZAUBIEhTNx>gT*R}%Eg>7Vnwm|$- z+VjUp$S&$Cbs{tDCV57r)LrTsk>$npmH|WxeI+N-43(V70vwWOL`q>&Co*2R#0ZIz zGCU(fic_NSlVLIV!9DLQ5{dML(?c1N1;isBag>ZtBp(fAy0KCxl24GF$iez8(86_X ziWFu5>CyxsGnylDE|3?I=6w8Myrn?KTSieLmya!y6Y0?(B_}d|isUA8{L|pFwhzdf z9t5(6M}SOl6v&KD0`X5dE%h@%#=9W(OH%($;$QT

&2> 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×=DL2~TmWH-&)Dv!Ni!#)6{~HFuVraC2ZE8ofqJt_f4~rFzQ@Gd# z!iM}Wh}0Gl7)x4h(8$G0EDn8pcB$w*tSxE%Z^q%ZN?_S1mET4CVA7Q0iY#=<#=3G0 z%zwVFTs01F;71)3b;b9RsGqp(W_-;(k4M!K%Q|ER-GGKGA#;$O8W3nd`p~lPyU9~hNu(ThA3g-{>&#WolBgp!(u8{g81UYt+Zh)EZhJ< z)C9z|y4!awKADSfQ{9zhH)kv50YXb#>G@VLiBE0ZjaG3on{9U;xLEXeupw{yX9e3AEM zwZ3Bu%#by=(>*|F8*ZiW?VP^yJBvS4@^(Z{7C=Q_$^?2@#Cdf7bGMv6AF;fR(GTlC z{O#)GkemaYp*hre2Y%b+Pz0W3t36ccM~w_tL;Q+<*ya}as?aUkQfmm3ty6~-oI)LI z>wn5k(Q^x`m^3I<@7h887-!y4PGLJS3J{&lvU+Dq*om~H%PuahN88z_|6XlUO9!kB z#~LdHomj4kC25HBS#09$%~q_$0GJ7|Zu=>ZNV|lZ7$P<)fB=u#MOSuW{yzl}Ec%@B z;!fhg<2_jJg025U^6j%-^xiJ8X&|QVEED9AF?He5iKof|!oU_p8tl}S#%#&%m|d_z zyx}UWVM{7e3Ar>j)b;4aE^PeqC70d0Kx1*kEFvslSOhjZ5t~EJ)&MCC|JnFZTzfAq z*$vG)xR;LY2BDMJaFgfys%pz`H~u^qZHD+Ecf>locn|t|W-p%`E{Ke;_2^l>Tt*b% z%{OHiwc7(D@iBd|M>kPh@muoR3tQ#=EvKRXl>;+2Z(e6W1$DL+ixM`?j4ius_{KK5 zM-^0xB!d}+*DGu1?x#$KYt?=pajkw{CgYENr|Kbx!@lss(QHSFGjK(9>SA~_eC1c> zlB4^nV6U!rb+HYpIJZQJ3TZNLYOC_w<9;LL1ksl5;L`Vi>aH9*KAp08WvY4ChV30| zwzrgYD#;zy)V$;gy(?`Z--Ei66X3vJvn#C;2w=0V4XwAf_`Oz{!;Ap|raW=3`;09T_B9|2v-ZQ5Rzg;XUv3&BanI4gvUNv3J zJg~i=&-NZ4%XoN#8QnBAgr(9cb!WCb5z={%5>YDN!fpjhs-WcS50hrx3qFLEOX#V> z1>2iptcBOSPLVLir^r4lYmqmvR3xZSv=bbpP5++zPeiop1VeV`A4AGP{5c!fWM|dQ#+xKBp&Bn$SyvNs|2{V&(n4=w>uXr^zaDw(S(aks zuafcqn(?HtX6N3u0fz=dVzL2^L)GsucR;sDls z11~9R@_%Z=k~xUgR5#=D*$|hd1ch;`>ndsmEyc&-h(@ zj2!;L@7iOuk3H`>MqYtf%Kg(Z?%FH+Jmfm{4^ijO)vlWXtL?9gK zlH-)!0`tc8$LYN@tZ#S*#oFBC)Em#nJe963ZR_3qSw8^z7X>#ce}ob?jXvjlqvN7G zBe0E(t*Hq>tNIg^#p=4B;C=GVb#&65Xo4@h0^&WCu(~yyOk3YK^LlMF)I zm;8w%_wxQ><;D0{TRDmVkmZIFMzFWNPfiK{=Y@v&-Jhr(>S`POL<7(Aa0i(ScP);# zx%Uk3(|@8|l*?ZOgBvj1nEd5|7b#^9ADZG<>{=GL0DR0K-W>i#_j0Y*(H|*j@_%203-HjouXLOm4gAn zR(xEXn0DmHyqRpP3_A}AqXU{U0ioe7Yn9E{l{BZRfHhfshb%H2NF5N=WhTO%k@PmQK`<+AX z`$N}4g=9-SN-LE>uZqOe1hd01*F8(#tpV5S zEICxbZ}+p5_ZQw5YMIekT{KtR`ic@>qqd0<%V)Jqq5(^~1)+jK3j$1NEjwZXXCl=7 zJQw)XqbD76D1l{enZSenbLlRQ3|Z=wI~*dzNsJiQ1L0Z~y+oG^L9 zRdvdvv=?S;WinL4+KC1r7+w!JixktT5<-I>U~q&fgbB$6knD`rrp%|V`YUU?-Vo0g zNwc%>;Ex6UDq6WVAera>W75hJG&8ca84`LF=ED?RCA{%f>Z!%E=s>5d4dd0&+ zwpw@vVQxh)L=eJ}DW6V?coES{=WFC&9Se62c2GN|pMs2{7~Hi&ba*D+_P7gkU}-H> zyJU+2@LQG!#JU98;x!Pcmg8SEQPe8SGk)WN&kw0n3%<8fq`b%9fPexlZ?5>-p&@ zLL#1Dr+pIW_m@KJKAdKofk6URDF8LIMOy7^uvDV0+4e;VfE@?dmja zd3ys})X#(#5v&LSY*S@&uC}89>-3ta5qz}h?pq*M1x`MFi_<^(W0w=%O6*yo&>z5h ze(M&^)x3oW3JWP@$oaEpN!@@#K4z-#I@!<^i8%{EGn;0`y++@!JKDB=Q z>0s&PsK?zD0+j=9#VGq0P`(qGDn>c1fV?Y0t2>}3`*53-7g~MZ%;wm@t}crz7L8j9 z<=_HJ4gkif1@ueBLh58=v?;+^A0)5-lV{W-9VUEv<;vaO3ZY6?nsd^u=w^c}c+5#w zW;B+uorTAixy;pjtGnx`of~Q(9HMtCzv< z>-&7{_V1if?do64Rw>LhvYFJO`{eD7$scCg=k8NFu)HS!s9KR?j)Ujl%nn1CIx%6X z`+zc8e$J9?PN%W?13vlh+;_V}%s2giRh0&jc_&q4TPK`!96f#Zbd_${MzyQfby`Ur z2>G=%O~Tj=0b<{1G=z*;ikQWFlfZ;Nm~8!j$ZB^sTv2rc9` zfOLYY{TA|D&w1a}e5pWUeUO25TbXM#e?S|UW!%oT zTTEz#)Tqf7+4hGcF=4adO3`_Qji|}exytl{N4r6zCy)DKz%C@KVZ3mDk>W&@G^=2S z;Mj&2bk7&!{2gk#!R+~5YBVn-;ucm3Vz;&8Q>_O8HXXmw>XK9J77Pfi`;t4uX=^{e z`}v}2?a>C#4q%z`CNHUcU43oSQq8F^sTrQN>RIyQ*HmulFSuoqZF^fWfyX#0zEn7> zqVs=w1GvD_zK@dsK74)R4lK7aZ^6onqqGo zGj9R6 zcVRt5Gor_0{P+Ev{FfyX!8WULDE|b^7cy8Htv7N@v?RzEWQ+Wk=$>GeFe@TRar=oL zG%e)(Z7AfwO8?1OI<9{wmi^MT{qhYB6)jVQUXUx?i6B>y&lTA7J0M`*dETg}p0ny) z!}1^~u!7*cPD59lLySbnL?Xr_A1wMQ^1!11B2O$#nBapDp-0Ebz2#f*!&2?VqmtC;^UVlgs0SAGm%G#^6nPh?b@N2mug+ zA_Pc8@#;635|bniZES1Bj@i;2L81j|CIJ?ttKSOTY>=!h9#&RFh?*20u6Hu=YFbI| z*;;g!Yfe~Fp$&S<7R6zO8YjS6V)_HV;RBDWd!XGCz%vzV|8QS+x3rONipb#XD;&Mj z0Q;}ttNB&Fr2r%D5>xiRD^)&!BJJ`#EB>^0aj%@!TmD}RCzDt!wd zS>KFR#pKDZ6x*Q6czn~-NrCZaI1A8=K&2C<1!@rrTRdBDT1{3IAzNaNan9rD8akPw zbLlLQB}Mh=6V)d++Wy&w5nXS;yp8^=XSP}00HPetVPk&)nD4kv=fRvF4$yIT^Tjp- z2e2lp!^QM#7m}L*HCqTqL$Pp%{#mNi3rN#`$vfMu>@CF=4Y=`dJEe!~-978%@Qr{? zf@Ad~mrb)*a4PGNBFkv#WVqfqAEC0;ey?p8SsIYywta^*GjYhV2F zF8)~78!hAD?6U3QF2u>2{5VWTNs0RW2*Vl&*L1AqF-RxS1+a@M=CU)?)$_{5*_z7n z;wn)#h+%U_L5xL{g-<7U)tSyIS9XBfK6+h@fO-tL%3;$nykcOMJCR=py}R*8CyjF3 z*0@hYhn5`j*;U>rb^}{JN_?3bu1|T_c~`xsyoBuntyGc5p>DC!Gc_U_{n3eDSp^hl zm2+ZwN4cn&&Lze9=|zs@E_`UP+_fpsKmH(Be6{p;1$QwLL3Zld^g@2XHnqpbUM?Ct z*ua#6?D-wSb&dx`onSjc$%iPpe`H(V7JlEe#DOYd*Ig(BhoKv*x@zni0b+0ihm74r zLTZ^ICZPnAkdz&@7kU2q&8JwN(+h8^;f*-|Ux!r>HSqlDkl`oXJ{vklsf&b0K;RbK z&BRXgzu-!9W01i38#S>vK4tvDmMkx!c9zoG=Au&QTC+|vTqc#@{WFx znGUOW2*B_K*%i7&z-s_nD|ClIRzI}*_IC&jL+f=}Uxz%b)@;d+7Q82HNm)z?&F&0b zD*#ao5L@&WI%U>vT!KTe-S|gvyEGt-_W;58CA+@c=StqO8KMV)cJH|J-uDmsq+~r! zi!Vhk7*1 z;D%W*K=dhD?z3o0s%wULA6GsB!Wah#*8cm)J9LPA==hNtVgX9LQBrNxwWf~WQeSh) z=E{_Xx*<0K0Rx)yB%;UCJDn!8vmjO4-95ORPP-U#)W`8meRGMh({clVFt$z%@eS)~ z$i_ZGQMs=N`F8_p(*S`aKuSM3Id<*luFW|FGZH&dQWYipfAh-B`d}=J_tl0kc~CN| zTS3;?y$gsrKbHSyi~e#UwF^ZFQ-rUt?9kcuvSa6Cs6)|5remlpPXmM>AjZsU*uDQq z`%nQ9V7FCX+#OPWS*G4hy8i(nlgO-x-4|YJ{AD5j*TfA~fHZdVqH=RV&J(hftKT?(ZGlf9sZY&Ci+ZM&XXEj~R0T!D#pFygg^v2S-MlA-+Zl>waEH zotbTO`pz_$T=b$?)E#73g|pB&u-fO(emE6whG>ow7t~E1zPpCDP01nHfsZ$AjdC~q z;#7bX?doSill<*{Q3nQu9U(GYE!nrU%0GiYn81b^vl?by7F3}Ew5Q!th3r4!UQ@Z2 zVY+gC1iJ;p-!8$M5vlwfFfuQU5J-bm#m1`T9qh}o`-8!+agu2v}Ocsz$jBCZ!tn?183)vsPmbb0kx}I{g3?ridV+I=$+N3S)4W7k-UY zU85W%7~3-C!e&ET7K*6X7_LDH+hTCykfHIRi_Q2@DT%U8xc3Wn2|s`(X>42tZ=l#6!ho{alGqh_otmCSpvZVl6tg?Gp=8T=>Eg1 zAr?9mdoMx@3nEN#Twa(Qa7;Y#12j9r{x#PLiFO5Ee!0(ER48=$4HLw-s3DrLY*IYq zCMG3{dJ~+8x&)kHySSf7;1lKQvrU12O>n59uzshE5Lk(y*DHzMh)^3KX$e?ej*l0t?7ve?lLE-4!uTfTp6BoRwact*ifRJ{0kT~a9wDA~ z`?Q9t^_C5sj6wN z=q_-H5q}LC>U2dgQPdF6a!NIgT^&%9x~EaXrI^oJDb(QDM8!onj_1}up-S*v?U*Gd zfzgsS_P%g^vnnVdovTin@MH9f$gfgE%=>xEs;sdRsy$Rc&>3N<;u7cA{di8zZH?+V zYpS)PHAD5@T4O6ZJXD{eoz{xlCc@z@Y(>e5aIaadG|Hy)ZU5vyC|&tz4IR$^!XZ-# zq>rM6%_BN|UQiI0dGeLH#8FGfP*=XKLTss_>*4?V)QV;Z`__wx0eP*~JTjg&@9*@S zYL0iz0PmxO1)NWNewG}Re)J!6No;Fs$Lh`m1Pk~>YJDAQc#+%646zO+Y?82GqTD@s z#9uzC$c*{Mq1+8I>9>NY}MmaHu2*5YnClRq77N zpoYUi*=V)hiyI+m^)N85;Uw#aT3V^6|cz&tNQ?u6t0y%27uub2Uu8%_B{zxGA(fD=gO66zj>#}={`L$&R ztyfpt%8K6WO24z;4qZvcosn|q2!1|a745a29SRB(RHP78^;Su_M_0}U?-6aY6P??# zeK7ufJsrO&!(BU|D`fze_I6kLW(;sY>`M9UxokI*#_A15*KQgmZ#!zoUm^PX?=!;H z9Z0uT+*^)JSkP?11~!9aiiANx-MFzFXDV=LZ~D=0Jj+(@-BQ!b{NAl_?N5=^cpR+S z=pM9h95CQ; zs%ResW~c{LiyO8;`mqW6+SL>LaruiL78?_nWM{XdQ_9$l4r`w}Xl|hddD;{{0mb_7 zW*n#a-ojzg8s+4>lxS*~f>vkYnMu`ngI|^JDwbedbRo;kQi)pzhL5H66m$fT6InegnerY1B7+&q(H>Ne|Vv9H=EY5 z|Eqb6?-lQ$l+0{Rb6H)h9!7!C!?Uo|g6Ej_nj7QJq?qegZ9|tBWsOyc)Wk1pT~2+! zoEZWh#bFAtTk|LZnAsZOQMflKd>S_|!D-w*SzT)$g+o|7jhmO?H0}+kD_ip@90DFC zk72jwQ8F810Bp$_l<$3w= znawR9Yc=@9$wb>OE{WvnFf{`5H~0d!I{H@D==oi*4SfPlW80fyoKy%cuVJ7&)Ad2j zGI^)!4IYz*b0>4crA^aSj|TmYYU~Rds^!zro;nw0W>8`Tr2?4U!m~RSey7z4jdFx* z)}>wf?+km71@Ed|D%zF}pQNVOlzDcjxt4+6wm-T9t!n?~bG^Gr{A(K|wh{WQ^GD z8*@s}Ru+WtLnu@l1&_6Dlc?1!efVoeMWva3n8mh34X10fVDRzP=(T5j8Y&G%48vS~ zsrZIJ-0AQ+4oXW#;DOZ*6g6A#!7k-X#!;Zg>@l40WAg_bPj$Kr%Zvzv?ILBFgU)8aXL_cHtJa2_Q7?bAwBZH~V4gnkYj!=mk;*{;*GYs9P1 zqjzlg>2U1cxiNTVUZGvHt|t<|t2b|kS{_=e-ij6LTl#&fmScOAP>fgGloD>xkJ2ED&U+Yg!)hwv9sn!~VUJRaI1vZP#{=f;F{uLIA z%c$0bQssGiuL&)O^7j`zk6qPuy?3PuJhQQy=+?RXKh8l{BJm7TQx4Ya)v?Kl`e!#S z;dnYGHrr0E+Dis~0L%2&Md?H3aj z8$Tc{vFO{FzVUIxEMF$XC&Z74PPBYIEIKy2Z&I{eTNP6jf#sI5|vcsRo{UzvHcRG<4AK*?@7-W z=qu9TCwdQ;5mCcq>-yGZKfWxq4Dw4FFos6?Nw{GBiC&^3f9WgJ;0OA$G~|)KI(w(5 zZVymnF$+WJeS8K@iS z{Uish0G47YEkx4MqyWi>-u08b=vzO@k+ug&UViLWpiu8|fiu|z;YjmB(Dv!Y0^J9}=(`!aUjs>^y!vRn$h+!=9>FL_TGT-D6xiMAcms4w zR47f4jp&7VbfajaRFT2vlv=1iuF1LjsXot@YJQ>jpnH3z@;Sa6^ha7!WK+q$850^I zAsD&BvLs&nDw%pF9SQNnl2`>Uxbca;2~ml|XyRzelg5Wj_H^NW=~qhbjQ(Uj)cY$9 zG%6&O7Wh?Jg@E8Nd4|4mBeqRa8Q7S(VKM!p6G?MNFDrOxPg7}e&Y)&e2~D~9K7*h) zzPK$dWLOT!~)Z$87D|0}R3Rnvy>? zeWBd8SDoq}Z+K^+9oDGkl{y9w^>$_-61O)}tlBfD=QeC1fn_}&gEDf=tu*ga&IU#rFs5RDJBtZ z39{9N{ig_6I|>Vt+EG@p)SI$>rIJiaTGHGQsXkYDD31>rMuEsQf2lk{Ox@`7P)2E2 zsVo*8)jg=MM%kW{o}ApI5alm_D<+C@Ar@m}rN|eY{{~1@g!iB8tL41!Ce@XgX3Dh5 zQ;N;0<0ZA!=QvcCGE3Fva%SQMe^kUVfXR#5a>flG(g)JgB|xf4<&EflzCYN!&M1{` zUPphP79}QWB;%UM%T^3ynSUV$L~haiGAchKAx}q%zdy6R=>Y zr&Njjx=9WtRSQM$JkeJn?~zhDs?r^P?6(C1KC_$Ts0pU4(J+3`xg#6*f9QtotMh*Z@@tUyE^ z@u#nm=I6l@dRN2H^qeV;h@)4}q;insU|PNb?s~=|y{uzFIQc)7d~!}dl`d%h56vn1 Avj6}9 delta 99010 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

a^|u^ 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 Rt#^8>3-hf5maD%|{10C`wW$CA diff --git a/frontend/components.json b/frontend/components.json new file mode 100644 index 0000000..98e21f0 --- /dev/null +++ b/frontend/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": false, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html index c73f00b..78ec50a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,6 +8,7 @@

+ diff --git a/frontend/package.json b/frontend/package.json index 8589c9a..a34ba5c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,9 +27,15 @@ "@babel/preset-react": "^7.24.1", "@babel/preset-typescript": "^7.24.1", "@codemirror/lang-python": "^6.1.5", + "@headlessui/react": "^2.1.8", "@jest/globals": "^29.7.0", "@nextui-org/react": "^2.2.9", "@radix-ui/react-context-menu": "^2.1.5", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.1.0", "@react-spring/web": "^9.7.3", "@rollup/rollup-linux-arm64-gnu": "4.13.0", "@testing-library/jest-dom": "^6.4.5", @@ -42,7 +48,10 @@ "@xyflow/react": "^12.2.0", "axios": "^1.6.7", "babel-jest": "^29.7.0", + "class-variance-authority": "^0.7.0", "classnames": "^2.5.1", + "clsx": "^2.1.1", + "cmdk": "1.0.0", "esbuild": "^0.21.4", "esbuild-wasm": "0.20.2", "framer-motion": "^11.0.6", @@ -52,7 +61,7 @@ "jest-fetch-mock": "^3.0.3", "jsdom": "^24.0.0", "lodash": "^4.17.21", - "lucide-react": "^0.343.0", + "lucide-react": "^0.445.0", "random-words": "^2.0.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -61,15 +70,20 @@ "react-router-dom": "^6.22.2", "react-test-renderer": "^18.3.1", "react-xarrows": "^2.0.2", + "tailwind-merge": "^2.5.2", + "tailwindcss-animate": "^1.0.7", + "tailwindcss-children": "^2.1.0", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", "uuid": "^9.0.1", + "vaul": "^0.9.4", "yaml": "^2.4.1" }, "devDependencies": { "@types/bun": "latest", "@types/jest": "^29.5.12", "@types/lodash": "^4.17.0", + "@types/node": "^22.5.5", "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", "@types/uuid": "^9.0.8", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ddae27b..310c378 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -3,6 +3,7 @@ import { ReactFlowProvider } from "@xyflow/react" import { RouterProvider, createBrowserRouter } from "react-router-dom" import { Preloader } from "./UI/Preloader/Preloader" import ContextWrapper from "./contexts" +import PopUpProvider from "./contexts/popUpContext" import { UndoRedoProvider } from "./contexts/undoRedoContext" import Fallback from "./pages/Fallback" import Flow from "./pages/Flow" @@ -10,7 +11,6 @@ import Home from "./pages/Home" import Index from "./pages/Index" const App = () => { - const router = createBrowserRouter([ { path: "/", @@ -26,9 +26,11 @@ const App = () => { path: "app/flow/:flowId", element: ( - - - + + + + + ), loader: Preloader, diff --git a/frontend/src/UI/Input/DefCombobox.tsx b/frontend/src/UI/Input/DefCombobox.tsx new file mode 100644 index 0000000..c44ad25 --- /dev/null +++ b/frontend/src/UI/Input/DefCombobox.tsx @@ -0,0 +1,132 @@ +import * as Popover from "@radix-ui/react-popover" +import classNames from "classnames" +import { CheckIcon } from "lucide-react" +import React, { ReactNode, useCallback, useEffect, useRef, useState } from "react" + +interface ComboboxProps { + items: string[] + placeholder?: string + selected: string + setSelected: (value: string) => void + startContent?: ReactNode // Дополнительный контент в начале input + endContent?: ReactNode // Дополнительный контент в конце input +} + +const DefCombobox: React.FC = ({ + selected, + setSelected, + items, + placeholder = "Select an option", + endContent, + startContent, +}) => { + const [inputValue, setInputValue] = useState("") + const [isOpen, setIsOpen] = useState(false) + const [filteredItems, setFilteredItems] = useState(items) + const [highlightedIndex, setHighlightedIndex] = useState(-1) + const containerRef = useRef(null) + const inputRef = useRef(null) + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value + setInputValue(value) + setFilteredItems(items.filter((item) => item.toLowerCase().includes(value.toLowerCase()))) + setHighlightedIndex(-1) // Сбрасываем выделение + setIsOpen(true) + } + + const handleSelectItem = useCallback( + (item: string) => { + setInputValue(item) + setSelected(item) + setIsOpen(false) + }, + [setSelected] + ) + + useEffect(() => { + if (isOpen && inputRef.current) { + inputRef.current.focus() // Ставим фокус обратно на input при открытии + } + }, [isOpen]) + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (isOpen) { + if (e.key === "ArrowDown") { + setHighlightedIndex((prev) => Math.min(prev + 1, filteredItems.length - 1)) + e.preventDefault() // Предотвращаем прокрутку страницы + } else if (e.key === "ArrowUp") { + setHighlightedIndex((prev) => Math.max(prev - 1, 0)) + e.preventDefault() // Предотвращаем прокрутку страницы + } else if (e.key === "Enter" && highlightedIndex >= 0) { + handleSelectItem(filteredItems[highlightedIndex]) + e.preventDefault() // Предотвращаем отправку формы, если она есть + } + } + } + window.addEventListener("keydown", handleKeyDown) + + return () => { + window.removeEventListener("keydown", handleKeyDown) + } + }, [isOpen, highlightedIndex, filteredItems, handleSelectItem]) + + return ( +
+
+ {startContent && {startContent}} + + {endContent && {endContent}} +
+ + {/* Popover for dropdown menu */} + + +
+ + + e.preventDefault()} + align='start' + side='bottom' + style={{ + width: containerRef.current?.offsetWidth ?? "320px", + }} + className={`mt-2 bg-background border border-input-border rounded-lg py-1 z-[9999] overflow-x-hidden *:text-sm`}> + {filteredItems.length ? ( + filteredItems.map((item, index) => ( +
handleSelectItem(item)}> + {item} + {selected === item && } +
+ )) + ) : ( +
No items found
+ )} +
+ + + {/* Стили для компонента */} +
+ ) +} + +export default DefCombobox diff --git a/frontend/src/UI/Input/DefInput.tsx b/frontend/src/UI/Input/DefInput.tsx new file mode 100644 index 0000000..18ae6e9 --- /dev/null +++ b/frontend/src/UI/Input/DefInput.tsx @@ -0,0 +1,52 @@ +import { Input, InputProps, InputSlots, SlotsToClasses } from "@nextui-org/react" +import classNames from "classnames" + + +// const DefInput = ({ className, label, labelClassName, wrapperClassName, ...props }: DefInputType) => { +// return ( +//
+// +// +//
+// ) +// } + +const defInputStyles: SlotsToClasses = { + label: "text-black/50 dark:text-white/90", + input: [ + "bg-transparent", + "placeholder:text-input-border-focus", + ], + innerWrapper: "bg-transparent", + inputWrapper: [ + "min-h-10 h-10", + "px-3.5", + "rounded-[8px]", + "shadow-none", + "bg-input-background", + "border border-input-border", + "hover:bg-transparent", + "group-data-[focus=true]:bg-input-background", + "group-data-[hover=true]:bg-input-background-disabled", + "!cursor-text", + ], +} + +const DefInput = ({ className, ...props }: InputProps) => { + return ( + + ) +} + +export default DefInput diff --git a/frontend/src/UI/Input/DefSelect.tsx b/frontend/src/UI/Input/DefSelect.tsx new file mode 100644 index 0000000..758a6ae --- /dev/null +++ b/frontend/src/UI/Input/DefSelect.tsx @@ -0,0 +1,101 @@ +import { CheckIcon, ChevronDownIcon } from "@radix-ui/react-icons" +import * as RadixSelect from "@radix-ui/react-select" +import classNames from "classnames" +import { motion } from "framer-motion" +import { useEffect, useState } from "react" + +type ItemSelectType = { + key: string + value: string + [key: string]: unknown +} + +type DefSelectProps = { + placeholder?: string + disabled?: boolean + className?: string + items: ItemSelectType[] + defaultValue?: string + onValueChange?: (value: string) => void + mini?: boolean +} + +const DefSelect = ({ + disabled = false, + className, + items, + defaultValue, + onValueChange, + placeholder, + mini = false, +}: DefSelectProps) => { + const [selectedValue, setSelectedValue] = useState(defaultValue || "") + + useEffect(() => { + setSelectedValue(defaultValue || "") + }, [defaultValue]) + + const handleChange = (value: string) => { + setSelectedValue(value) + if (onValueChange) { + onValueChange(value) + } + } + + return ( + + + + + + + + + + + + {items.map((item) => ( + + {item.value} + + + + + ))} + + + + + + ) +} + +export default DefSelect diff --git a/frontend/src/UI/Input/DefTextarea.tsx b/frontend/src/UI/Input/DefTextarea.tsx new file mode 100644 index 0000000..f228bcf --- /dev/null +++ b/frontend/src/UI/Input/DefTextarea.tsx @@ -0,0 +1,32 @@ +import { InputSlots, SlotsToClasses, Textarea, TextAreaProps } from "@nextui-org/react" +import classNames from "classnames" + +const defInputStyles: SlotsToClasses = { + label: "text-black/50 dark:text-white/90", + input: ["bg-transparent", "placeholder:text-input-border-focus"], + innerWrapper: "bg-transparent", + inputWrapper: [ + "min-h-10 h-10", + "px-3.5", + "rounded-[8px]", + "shadow-none", + "bg-input-background", + "border border-input-border", + "hover:bg-transparent", + "group-data-[focus=true]:bg-input-background", + "group-data-[hover=true]:bg-input-background-disabled", + "!cursor-text", + ], +} + +const DefTextarea = ({ className, ...props }: TextAreaProps) => { + return ( +