From 51ab7a88106df7e571749faedc2dc0da1bc81aa1 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 13:14:13 +0800 Subject: [PATCH 01/80] Split the rpc_agent module into multiple submodules --- .../distributed_basic/distributed_dialog.py | 2 +- .../distributed_debate/distributed_debate.py | 2 +- examples/distributed_simulation/main.py | 2 +- src/agentscope/agents/__init__.py | 3 +- src/agentscope/agents/rpc_agent.py | 622 +----------------- src/agentscope/message.py | 2 +- src/agentscope/server/__init__.py | 9 + src/agentscope/server/launcher.py | 344 ++++++++++ src/agentscope/server/servicer.py | 305 +++++++++ tests/rpc_agent_test.py | 2 +- 10 files changed, 667 insertions(+), 626 deletions(-) create mode 100644 src/agentscope/server/__init__.py create mode 100644 src/agentscope/server/launcher.py create mode 100644 src/agentscope/server/servicer.py diff --git a/examples/distributed_basic/distributed_dialog.py b/examples/distributed_basic/distributed_dialog.py index e558c54fa..ab0de4235 100644 --- a/examples/distributed_basic/distributed_dialog.py +++ b/examples/distributed_basic/distributed_dialog.py @@ -7,7 +7,7 @@ import agentscope from agentscope.agents.user_agent import UserAgent from agentscope.agents.dialog_agent import DialogAgent -from agentscope.agents.rpc_agent import RpcAgentServerLauncher +from agentscope.server import RpcAgentServerLauncher def parse_args() -> argparse.Namespace: diff --git a/examples/distributed_debate/distributed_debate.py b/examples/distributed_debate/distributed_debate.py index f5813e6f2..a4e0a4287 100644 --- a/examples/distributed_debate/distributed_debate.py +++ b/examples/distributed_debate/distributed_debate.py @@ -8,7 +8,7 @@ import agentscope from agentscope.agents import DialogAgent from agentscope.msghub import msghub -from agentscope.agents.rpc_agent import RpcAgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg from agentscope.utils.logging_utils import logger diff --git a/examples/distributed_simulation/main.py b/examples/distributed_simulation/main.py index 7fd0cf19b..bb26fe533 100644 --- a/examples/distributed_simulation/main.py +++ b/examples/distributed_simulation/main.py @@ -11,7 +11,7 @@ import agentscope from agentscope.agents import AgentBase -from agentscope.agents.rpc_agent import RpcAgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg diff --git a/src/agentscope/agents/__init__.py b/src/agentscope/agents/__init__.py index 0d5f6f84d..7bc5f83e5 100644 --- a/src/agentscope/agents/__init__.py +++ b/src/agentscope/agents/__init__.py @@ -6,7 +6,7 @@ from .dict_dialog_agent import DictDialogAgent from .user_agent import UserAgent from .text_to_image_agent import TextToImageAgent -from .rpc_agent import RpcAgent, RpcAgentServerLauncher +from .rpc_agent import RpcAgent from .react_agent import ReActAgent @@ -20,5 +20,4 @@ "ReActAgent", "DistConf", "RpcAgent", - "RpcAgentServerLauncher", ] diff --git a/src/agentscope/agents/rpc_agent.py b/src/agentscope/agents/rpc_agent.py index b7c3441bc..e4274f700 100644 --- a/src/agentscope/agents/rpc_agent.py +++ b/src/agentscope/agents/rpc_agent.py @@ -1,43 +1,15 @@ # -*- coding: utf-8 -*- """ Base class for Rpc Agent """ - -from multiprocessing import Process, Event, Pipe -from multiprocessing.synchronize import Event as EventClass -import socket -import threading -import json -import base64 -import traceback -import asyncio -from typing import Any, Type, Optional, Union, Sequence -from concurrent import futures +from typing import Type, Optional, Union, Sequence from loguru import logger -try: - import dill - import grpc - from grpc import ServicerContext - from expiringdict import ExpiringDict -except ImportError: - dill = None - grpc = None - ServicerContext = Any - ExpiringDict = None - -from agentscope._init import init_process, _INIT_SETTINGS from agentscope.agents.agent import AgentBase from agentscope.message import ( - Msg, PlaceholderMessage, - deserialize, serialize, ) -from agentscope.rpc import ( - RpcAgentClient, - RpcMsg, - RpcAgentServicer, - add_RpcAgentServicer_to_server, -) +from agentscope.rpc import RpcAgentClient +from agentscope.server.launcher import RpcAgentServerLauncher def rpc_servicer_method( # type: ignore[no-untyped-def] @@ -217,591 +189,3 @@ def stop(self) -> None: def __del__(self) -> None: self.stop() - - -def setup_rpc_agent_server( - host: str, - port: int, - init_settings: dict = None, - start_event: EventClass = None, - stop_event: EventClass = None, - pipe: int = None, - local_mode: bool = True, - max_pool_size: int = 8192, - max_timeout_seconds: int = 1800, - custom_agents: list = None, -) -> None: - """Setup gRPC server rpc agent. - - Args: - host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. - port (`int`): - The socket port monitored by grpc server. - init_settings (`dict`, defaults to `None`): - Init settings for agentscope.init. - start_event (`EventClass`, defaults to `None`): - An Event instance used to determine whether the child process - has been started. - stop_event (`EventClass`, defaults to `None`): - The stop Event instance used to determine whether the child - process has been stopped. - pipe (`int`, defaults to `None`): - A pipe instance used to pass the actual port of the server. - local_mode (`bool`, defaults to `None`): - Only listen to local requests. - max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. - max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. - custom_agents (`list`, defaults to `None`): - A list of custom agent classes that are not in `agentscope.agents`. - """ - asyncio.run( - setup_rpc_agent_server_async( - host=host, - port=port, - init_settings=init_settings, - start_event=start_event, - stop_event=stop_event, - pipe=pipe, - local_mode=local_mode, - max_pool_size=max_pool_size, - max_timeout_seconds=max_timeout_seconds, - custom_agents=custom_agents, - ), - ) - - -async def setup_rpc_agent_server_async( - host: str, - port: int, - init_settings: dict = None, - start_event: EventClass = None, - stop_event: EventClass = None, - pipe: int = None, - local_mode: bool = True, - max_pool_size: int = 8192, - max_timeout_seconds: int = 1800, - custom_agents: list = None, -) -> None: - """Setup gRPC server rpc agent in an async way. - - Args: - host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. - port (`int`): - The socket port monitored by grpc server. - init_settings (`dict`, defaults to `None`): - Init settings for agentscope.init. - start_event (`EventClass`, defaults to `None`): - An Event instance used to determine whether the child process - has been started. - stop_event (`EventClass`, defaults to `None`): - The stop Event instance used to determine whether the child - process has been stopped. - pipe (`int`, defaults to `None`): - A pipe instance used to pass the actual port of the server. - local_mode (`bool`, defaults to `None`): - Only listen to local requests. - max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. - max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. - custom_agents (`list`, defaults to `None`): - A list of custom agent classes that are not in `agentscope.agents`. - """ - - if init_settings is not None: - init_process(**init_settings) - servicer = AgentPlatform( - host=host, - port=port, - max_pool_size=max_pool_size, - max_timeout_seconds=max_timeout_seconds, - ) - # update agent registry - if custom_agents is not None: - for agent_class in custom_agents: - AgentBase.register_agent_class(agent_class=agent_class) - while True: - try: - port = check_port(port) - servicer.port = port - logger.info( - f"Starting rpc server at port [{port}]...", - ) - server = grpc.aio.server( - futures.ThreadPoolExecutor(max_workers=None), - ) - add_RpcAgentServicer_to_server(servicer, server) - if local_mode: - server.add_insecure_port(f"localhost:{port}") - else: - server.add_insecure_port(f"0.0.0.0:{port}") - await server.start() - break - except OSError: - logger.warning( - f"Failed to start rpc server at port [{port}]" - f"try another port", - ) - logger.info( - f"rpc server at port [{port}] started successfully", - ) - if start_event is not None: - pipe.send(port) - start_event.set() - while not stop_event.is_set(): - await asyncio.sleep(1) - logger.info( - f"Stopping rpc server at port [{port}]", - ) - await server.stop(10.0) - else: - await server.wait_for_termination() - logger.info( - f"rpc server at port [{port}] stopped successfully", - ) - - -def find_available_port() -> int: - """Get an unoccupied socket port number.""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(("", 0)) - return s.getsockname()[1] - - -def check_port(port: Optional[int] = None) -> int: - """Check if the port is available. - - Args: - port (`int`): - the port number being checked. - - Returns: - `int`: the port number that passed the check. If the port is found - to be occupied, an available port number will be automatically - returned. - """ - if port is None: - new_port = find_available_port() - logger.warning( - "gRpc server port is not provided, automatically select " - f"[{new_port}] as the port number.", - ) - return new_port - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex(("localhost", port)) == 0: - new_port = find_available_port() - logger.warning( - f"Port [{port}] is occupied, use [{new_port}] instead", - ) - return new_port - return port - - -class RpcAgentServerLauncher: - """The launcher of AgentPlatform (formerly RpcAgentServer).""" - - def __init__( - self, - host: str = "localhost", - port: int = None, - max_pool_size: int = 8192, - max_timeout_seconds: int = 1800, - local_mode: bool = False, - custom_agents: list = None, - agent_class: Type[AgentBase] = None, - agent_args: tuple = (), - agent_kwargs: dict = None, - ) -> None: - """Init a rpc agent server launcher. - - Args: - host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. - port (`int`, defaults to `None`): - Port of the rpc agent server. - max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. - max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. - local_mode (`bool`, defaults to `False`): - Whether the started rpc server only listens to local - requests. - custom_agents (`list`, defaults to `None`): - A list of custom agent classes that are not in - `agentscope.agents`. - agent_class (`Type[AgentBase]`, deprecated): - The AgentBase subclass encapsulated by this wrapper. - agent_args (`tuple`, deprecated): The args tuple used to - initialize the agent_class. - agent_kwargs (`dict`, deprecated): The args dict used to - initialize the agent_class. - """ - self.host = host - self.port = check_port(port) - self.max_pool_size = max_pool_size - self.max_timeout_seconds = max_timeout_seconds - self.local_mode = local_mode - self.server = None - self.stop_event = None - self.parent_con = None - self.custom_agents = custom_agents - if ( - agent_class is not None - or len(agent_args) > 0 - or agent_kwargs is not None - ): - logger.warning( - "`agent_class`, `agent_args` and `agent_kwargs` is deprecated" - " in `RpcAgentServerLauncher`", - ) - - def _launch_in_main(self) -> None: - """Launch gRPC server in main-process""" - logger.info( - f"Launching agent server at [{self.host}:{self.port}]...", - ) - asyncio.run( - setup_rpc_agent_server_async( - host=self.host, - port=self.port, - max_pool_size=self.max_pool_size, - max_timeout_seconds=self.max_timeout_seconds, - local_mode=self.local_mode, - custom_agents=self.custom_agents, - ), - ) - - def _launch_in_sub(self) -> None: - """Launch gRPC server in sub-process.""" - self.stop_event = Event() - self.parent_con, child_con = Pipe() - start_event = Event() - server_process = Process( - target=setup_rpc_agent_server, - kwargs={ - "host": self.host, - "port": self.port, - "init_settings": _INIT_SETTINGS, - "start_event": start_event, - "stop_event": self.stop_event, - "pipe": child_con, - "max_pool_size": self.max_pool_size, - "max_timeout_seconds": self.max_timeout_seconds, - "local_mode": self.local_mode, - "custom_agents": self.custom_agents, - }, - ) - server_process.start() - self.port = self.parent_con.recv() - start_event.wait() - self.server = server_process - logger.info( - f"Launch agent server at [{self.host}:{self.port}] success", - ) - - def launch(self, in_subprocess: bool = True) -> None: - """launch a rpc agent server. - - Args: - in_subprocess (bool, optional): launch the server in subprocess. - Defaults to True. For agents that need to obtain command line - input, such as UserAgent, please set this value to False. - """ - if in_subprocess: - self._launch_in_sub() - else: - self._launch_in_main() - - def wait_until_terminate(self) -> None: - """Wait for server process""" - if self.server is not None: - self.server.join() - - def shutdown(self) -> None: - """Shutdown the rpc agent server.""" - if self.server is not None: - if self.stop_event is not None: - self.stop_event.set() - self.stop_event = None - self.server.join() - if self.server.is_alive(): - self.server.kill() - logger.info( - f"Agent server at port [{self.port}] is killed.", - ) - self.server = None - - -class AgentPlatform(RpcAgentServicer): - """A platform for agent to run on (formerly RpcServerSideWrapper)""" - - def __init__( - self, - host: str = "localhost", - port: int = None, - max_pool_size: int = 8192, - max_timeout_seconds: int = 1800, - ): - """Init the AgentPlatform. - - Args: - host (`str`, defaults to "localhost"): - Hostname of the rpc agent server. - port (`int`, defaults to `None`): - Port of the rpc agent server. - max_pool_size (`int`, defaults to `8192`): - The max number of task results that the server can - accommodate. Note that the oldest result will be deleted - after exceeding the pool size. - max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. Note that expired results will be - deleted. - """ - self.host = host - self.port = port - self.result_pool = ExpiringDict( - max_len=max_pool_size, - max_age_seconds=max_timeout_seconds, - ) - self.executor = futures.ThreadPoolExecutor(max_workers=None) - self.task_id_lock = threading.Lock() - self.agent_id_lock = threading.Lock() - self.task_id_counter = 0 - self.agent_pool: dict[str, AgentBase] = {} - - def get_task_id(self) -> int: - """Get the auto-increment task id.""" - with self.task_id_lock: - self.task_id_counter += 1 - return self.task_id_counter - - def agent_exists(self, agent_id: str) -> bool: - """Check whether the agent exists. - - Args: - agent_id (`str`): the agent id. - - Returns: - bool: whether the agent exists. - """ - return agent_id in self.agent_pool - - def check_and_generate_agent( - self, - agent_id: str, - agent_configs: dict, - ) -> None: - """ - Check whether the agent exists, and create new agent instance - for new agent. - - Args: - agent_id (`str`): the agent id. - agent_configs (`dict`): configuration used to initialize the agent, - with three fields (generated in `_AgentMeta`): - - .. code-block:: python - - { - "class_name": {name of the agent} - "args": {args in tuple type to init the agent} - "kwargs": {args in dict type to init the agent} - } - - """ - with self.agent_id_lock: - if agent_id not in self.agent_pool: - agent_class_name = agent_configs["class_name"] - agent_instance = AgentBase.get_agent_class(agent_class_name)( - *agent_configs["args"], - **agent_configs["kwargs"], - ) - agent_instance._agent_id = agent_id # pylint: disable=W0212 - self.agent_pool[agent_id] = agent_instance - logger.info(f"create agent instance [{agent_id}]") - - def check_and_delete_agent(self, agent_id: str) -> None: - """ - Check whether the agent exists, and delete the agent instance - for the agent_id. - - Args: - agent_id (`str`): the agent id. - """ - with self.agent_id_lock: - if agent_id in self.agent_pool: - self.agent_pool.pop(agent_id) - logger.info(f"delete agent instance [{agent_id}]") - - def call_func( # pylint: disable=W0236 - self, - request: RpcMsg, - context: ServicerContext, - ) -> RpcMsg: - """Call the specific servicer function.""" - if hasattr(self, request.target_func): - if request.target_func not in ["_create_agent", "_get"]: - if not self.agent_exists(request.agent_id): - return context.abort( - grpc.StatusCode.INVALID_ARGUMENT, - f"Agent [{request.agent_id}] not exists.", - ) - return getattr(self, request.target_func)(request) - else: - # TODO: support other user defined method - logger.error(f"Unsupported method {request.target_func}") - return context.abort( - grpc.StatusCode.INVALID_ARGUMENT, - f"Unsupported method {request.target_func}", - ) - - def _reply(self, request: RpcMsg) -> RpcMsg: - """Call function of RpcAgentService - - Args: - request (`RpcMsg`): - Message containing input parameters or input parameter - placeholders. - - Returns: - `RpcMsg`: A serialized Msg instance with attributes name, host, - port and task_id - """ - if request.value: - msg = deserialize(request.value) - else: - msg = None - task_id = self.get_task_id() - self.result_pool[task_id] = threading.Condition() - self.executor.submit( - self.process_messages, - task_id, - request.agent_id, - msg, # type: ignore[arg-type] - ) - return RpcMsg( - value=Msg( - name=self.agent_pool[request.agent_id].name, - content=None, - task_id=task_id, - ).serialize(), - ) - - def _get(self, request: RpcMsg) -> RpcMsg: - """Get function of RpcAgentService - - Args: - request (`RpcMsg`): - Identifier of message, with json format:: - - { - 'task_id': int - } - - Returns: - `RpcMsg`: Concrete values of the specific message (or part of it). - """ - msg = json.loads(request.value) - while True: - result = self.result_pool.get(msg["task_id"]) - if isinstance(result, threading.Condition): - with result: - result.wait(timeout=1) - else: - break - return RpcMsg(value=result.serialize()) - - def _observe(self, request: RpcMsg) -> RpcMsg: - """Observe function of RpcAgentService - - Args: - request (`RpcMsg`): - The serialized input to be observed. - - Returns: - `RpcMsg`: Empty RpcMsg. - """ - msgs = deserialize(request.value) - for msg in msgs: - if isinstance(msg, PlaceholderMessage): - msg.update_value() - self.agent_pool[request.agent_id].observe(msgs) - return RpcMsg() - - def _create_agent(self, request: RpcMsg) -> RpcMsg: - """Create a new agent instance for the agent_id. - - Args: - request (RpcMsg): request message with a `agent_id` field. - """ - self.check_and_generate_agent( - request.agent_id, - agent_configs=( - dill.loads(base64.b64decode(request.value)) - if request.value - else None - ), - ) - return RpcMsg() - - def _clone_agent(self, request: RpcMsg) -> RpcMsg: - """Clone a new agent instance from the origin instance. - - Args: - request (RpcMsg): The `agent_id` field is the agent_id of the - agent to be cloned. - - Returns: - `RpcMsg`: The `value` field contains the agent_id of generated - agent. - """ - agent_id = request.agent_id - with self.agent_id_lock: - if agent_id not in self.agent_pool: - raise ValueError(f"Agent [{agent_id}] not exists") - ori_agent = self.agent_pool[agent_id] - new_agent = ori_agent.__class__( - *ori_agent._init_settings["args"], # pylint: disable=W0212 - **ori_agent._init_settings["kwargs"], # pylint: disable=W0212 - ) - with self.agent_id_lock: - self.agent_pool[new_agent.agent_id] = new_agent - return RpcMsg(value=new_agent.agent_id) - - def _delete_agent(self, request: RpcMsg) -> RpcMsg: - """Delete the agent instance of the specific sesssion_id. - - Args: - request (RpcMsg): request message with a `agent_id` field. - """ - self.check_and_delete_agent(request.agent_id) - return RpcMsg() - - def process_messages( - self, - task_id: int, - agent_id: str, - task_msg: dict = None, - ) -> None: - """Task processing.""" - if isinstance(task_msg, PlaceholderMessage): - task_msg.update_value() - cond = self.result_pool[task_id] - try: - result = self.agent_pool[agent_id].reply(task_msg) - self.result_pool[task_id] = result - except Exception: - error_msg = traceback.format_exc() - logger.error(f"Error in agent [{agent_id}]:\n{error_msg}") - self.result_pool[task_id] = Msg( - name="ERROR", - role="assistant", - __status="ERROR", - content=f"Error in agent [{agent_id}]:\n{error_msg}", - ) - with cond: - cond.notify_all() diff --git a/src/agentscope/message.py b/src/agentscope/message.py index 30f35fe61..4a1df7b91 100644 --- a/src/agentscope/message.py +++ b/src/agentscope/message.py @@ -391,7 +391,7 @@ def serialize(self) -> str: } -def deserialize(s: str) -> Union[MessageBase, Sequence]: +def deserialize(s: Union[str, bytes]) -> Union[MessageBase, Sequence]: """Deserialize json string into MessageBase""" js_msg = json.loads(s) msg_type = js_msg.pop("__type") diff --git a/src/agentscope/server/__init__.py b/src/agentscope/server/__init__.py new file mode 100644 index 000000000..18b2ad790 --- /dev/null +++ b/src/agentscope/server/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +"""Import all server related modules in the package.""" +from .launcher import RpcAgentServerLauncher +from .servicer import AgentPlatform + +__all__ = [ + "RpcAgentServerLauncher", + "AgentPlatform", +] diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py new file mode 100644 index 000000000..387552115 --- /dev/null +++ b/src/agentscope/server/launcher.py @@ -0,0 +1,344 @@ +# -*- coding: utf-8 -*- +""" Server of distributed agent""" +from multiprocessing import Process, Event, Pipe +from multiprocessing.synchronize import Event as EventClass +import socket +import asyncio +from typing import Any, Type, Optional +from concurrent import futures +from loguru import logger + +try: + import grpc +except ImportError: + grpc = None + +from .servicer import AgentPlatform +from ..agents.agent import AgentBase + +try: + from ..rpc.rpc_agent_pb2_grpc import ( + add_RpcAgentServicer_to_server, + ) +except ModuleNotFoundError: + add_RpcAgentServicer_to_server = Any + + +def setup_rpc_agent_server( + host: str, + port: int, + init_settings: dict = None, + start_event: EventClass = None, + stop_event: EventClass = None, + pipe: int = None, + local_mode: bool = True, + max_pool_size: int = 8192, + max_timeout_seconds: int = 1800, + custom_agents: list = None, +) -> None: + """Setup gRPC server rpc agent. + + Args: + host (`str`, defaults to `"localhost"`): + Hostname of the rpc agent server. + port (`int`): + The socket port monitored by grpc server. + init_settings (`dict`, defaults to `None`): + Init settings for agentscope.init. + start_event (`EventClass`, defaults to `None`): + An Event instance used to determine whether the child process + has been started. + stop_event (`EventClass`, defaults to `None`): + The stop Event instance used to determine whether the child + process has been stopped. + pipe (`int`, defaults to `None`): + A pipe instance used to pass the actual port of the server. + local_mode (`bool`, defaults to `None`): + Only listen to local requests. + max_pool_size (`int`, defaults to `8192`): + Max number of task results that the server can accommodate. + max_timeout_seconds (`int`, defaults to `1800`): + Timeout for task results. + custom_agents (`list`, defaults to `None`): + A list of custom agent classes that are not in `agentscope.agents`. + """ + asyncio.run( + setup_rpc_agent_server_async( + host=host, + port=port, + init_settings=init_settings, + start_event=start_event, + stop_event=stop_event, + pipe=pipe, + local_mode=local_mode, + max_pool_size=max_pool_size, + max_timeout_seconds=max_timeout_seconds, + custom_agents=custom_agents, + ), + ) + + +async def setup_rpc_agent_server_async( + host: str, + port: int, + init_settings: dict = None, + start_event: EventClass = None, + stop_event: EventClass = None, + pipe: int = None, + local_mode: bool = True, + max_pool_size: int = 8192, + max_timeout_seconds: int = 1800, + custom_agents: list = None, +) -> None: + """Setup gRPC server rpc agent in an async way. + + Args: + host (`str`, defaults to `"localhost"`): + Hostname of the rpc agent server. + port (`int`): + The socket port monitored by grpc server. + init_settings (`dict`, defaults to `None`): + Init settings for agentscope.init. + start_event (`EventClass`, defaults to `None`): + An Event instance used to determine whether the child process + has been started. + stop_event (`EventClass`, defaults to `None`): + The stop Event instance used to determine whether the child + process has been stopped. + pipe (`int`, defaults to `None`): + A pipe instance used to pass the actual port of the server. + local_mode (`bool`, defaults to `None`): + Only listen to local requests. + max_pool_size (`int`, defaults to `8192`): + Max number of task results that the server can accommodate. + max_timeout_seconds (`int`, defaults to `1800`): + Timeout for task results. + custom_agents (`list`, defaults to `None`): + A list of custom agent classes that are not in `agentscope.agents`. + """ + from agentscope._init import init_process + + if init_settings is not None: + init_process(**init_settings) + servicer = AgentPlatform( + host=host, + port=port, + max_pool_size=max_pool_size, + max_timeout_seconds=max_timeout_seconds, + ) + # update agent registry + if custom_agents is not None: + for agent_class in custom_agents: + AgentBase.register_agent_class(agent_class=agent_class) + while True: + try: + port = check_port(port) + servicer.port = port + logger.info( + f"Starting rpc server at port [{port}]...", + ) + server = grpc.aio.server( + futures.ThreadPoolExecutor(max_workers=None), + ) + add_RpcAgentServicer_to_server(servicer, server) + if local_mode: + server.add_insecure_port(f"localhost:{port}") + else: + server.add_insecure_port(f"0.0.0.0:{port}") + await server.start() + break + except OSError: + logger.warning( + f"Failed to start rpc server at port [{port}]" + f"try another port", + ) + logger.info( + f"rpc server at port [{port}] started successfully", + ) + if start_event is not None: + pipe.send(port) + start_event.set() + while not stop_event.is_set(): + await asyncio.sleep(1) + logger.info( + f"Stopping rpc server at port [{port}]", + ) + await server.stop(10.0) + else: + await server.wait_for_termination() + logger.info( + f"rpc server at port [{port}] stopped successfully", + ) + + +def find_available_port() -> int: + """Get an unoccupied socket port number.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + +def check_port(port: Optional[int] = None) -> int: + """Check if the port is available. + + Args: + port (`int`): + the port number being checked. + + Returns: + `int`: the port number that passed the check. If the port is found + to be occupied, an available port number will be automatically + returned. + """ + if port is None: + new_port = find_available_port() + logger.warning( + "gRpc server port is not provided, automatically select " + f"[{new_port}] as the port number.", + ) + return new_port + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + if s.connect_ex(("localhost", port)) == 0: + new_port = find_available_port() + logger.warning( + f"Port [{port}] is occupied, use [{new_port}] instead", + ) + return new_port + return port + + +class RpcAgentServerLauncher: + """The launcher of AgentPlatform (formerly RpcAgentServer).""" + + def __init__( + self, + host: str = "localhost", + port: int = None, + max_pool_size: int = 8192, + max_timeout_seconds: int = 1800, + local_mode: bool = False, + custom_agents: list = None, + agent_class: Type[AgentBase] = None, + agent_args: tuple = (), + agent_kwargs: dict = None, + ) -> None: + """Init a rpc agent server launcher. + + Args: + host (`str`, defaults to `"localhost"`): + Hostname of the rpc agent server. + port (`int`, defaults to `None`): + Port of the rpc agent server. + max_pool_size (`int`, defaults to `8192`): + Max number of task results that the server can accommodate. + max_timeout_seconds (`int`, defaults to `1800`): + Timeout for task results. + local_mode (`bool`, defaults to `False`): + Whether the started rpc server only listens to local + requests. + custom_agents (`list`, defaults to `None`): + A list of custom agent classes that are not in + `agentscope.agents`. + agent_class (`Type[AgentBase]`, deprecated): + The AgentBase subclass encapsulated by this wrapper. + agent_args (`tuple`, deprecated): The args tuple used to + initialize the agent_class. + agent_kwargs (`dict`, deprecated): The args dict used to + initialize the agent_class. + """ + self.host = host + self.port = check_port(port) + self.max_pool_size = max_pool_size + self.max_timeout_seconds = max_timeout_seconds + self.local_mode = local_mode + self.server = None + self.stop_event = None + self.parent_con = None + self.custom_agents = custom_agents + if ( + agent_class is not None + or len(agent_args) > 0 + or agent_kwargs is not None + ): + logger.warning( + "`agent_class`, `agent_args` and `agent_kwargs` is deprecated" + " in `RpcAgentServerLauncher`", + ) + + def _launch_in_main(self) -> None: + """Launch gRPC server in main-process""" + logger.info( + f"Launching agent server at [{self.host}:{self.port}]...", + ) + asyncio.run( + setup_rpc_agent_server_async( + host=self.host, + port=self.port, + max_pool_size=self.max_pool_size, + max_timeout_seconds=self.max_timeout_seconds, + local_mode=self.local_mode, + custom_agents=self.custom_agents, + ), + ) + + def _launch_in_sub(self) -> None: + """Launch gRPC server in sub-process.""" + from agentscope._init import _INIT_SETTINGS + + self.stop_event = Event() + self.parent_con, child_con = Pipe() + start_event = Event() + server_process = Process( + target=setup_rpc_agent_server, + kwargs={ + "host": self.host, + "port": self.port, + "init_settings": _INIT_SETTINGS, + "start_event": start_event, + "stop_event": self.stop_event, + "pipe": child_con, + "max_pool_size": self.max_pool_size, + "max_timeout_seconds": self.max_timeout_seconds, + "local_mode": self.local_mode, + "custom_agents": self.custom_agents, + }, + ) + server_process.start() + self.port = self.parent_con.recv() + start_event.wait() + self.server = server_process + logger.info( + f"Launch agent server at [{self.host}:{self.port}] success", + ) + + def launch(self, in_subprocess: bool = True) -> None: + """launch a rpc agent server. + + Args: + in_subprocess (bool, optional): launch the server in subprocess. + Defaults to True. For agents that need to obtain command line + input, such as UserAgent, please set this value to False. + """ + if in_subprocess: + self._launch_in_sub() + else: + self._launch_in_main() + + def wait_until_terminate(self) -> None: + """Wait for server process""" + if self.server is not None: + self.server.join() + + def shutdown(self) -> None: + """Shutdown the rpc agent server.""" + if self.server is not None: + if self.stop_event is not None: + self.stop_event.set() + self.stop_event = None + self.server.join() + if self.server.is_alive(): + self.server.kill() + logger.info( + f"Agent server at port [{self.port}] is killed.", + ) + self.server = None diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py new file mode 100644 index 000000000..b178720b5 --- /dev/null +++ b/src/agentscope/server/servicer.py @@ -0,0 +1,305 @@ +# -*- coding: utf-8 -*- +""" Server of distributed agent""" +import threading +import base64 +import json +import traceback +from typing import Any +from concurrent import futures +from loguru import logger + +try: + import dill + import grpc + from grpc import ServicerContext + from expiringdict import ExpiringDict +except ImportError: + dill = None + grpc = None + ServicerContext = Any + ExpiringDict = None + +from ..agents.agent import AgentBase +from ..message import ( + Msg, + PlaceholderMessage, + deserialize, +) + +try: + from ..rpc.rpc_agent_pb2 import RpcMsg # pylint: disable=E0611 + from ..rpc.rpc_agent_pb2_grpc import RpcAgentServicer +except ModuleNotFoundError: + RpcMsg = Any # type: ignore[misc] + RpcAgentServicer = Any + + +class AgentPlatform(RpcAgentServicer): + """A platform for agent to run on (formerly RpcServerSideWrapper)""" + + def __init__( + self, + host: str = "localhost", + port: int = None, + max_pool_size: int = 8192, + max_timeout_seconds: int = 1800, + ): + """Init the AgentPlatform. + + Args: + host (`str`, defaults to "localhost"): + Hostname of the rpc agent server. + port (`int`, defaults to `None`): + Port of the rpc agent server. + max_pool_size (`int`, defaults to `8192`): + The max number of task results that the server can + accommodate. Note that the oldest result will be deleted + after exceeding the pool size. + max_timeout_seconds (`int`, defaults to `1800`): + Timeout for task results. Note that expired results will be + deleted. + """ + self.host = host + self.port = port + self.result_pool = ExpiringDict( + max_len=max_pool_size, + max_age_seconds=max_timeout_seconds, + ) + self.executor = futures.ThreadPoolExecutor(max_workers=None) + self.task_id_lock = threading.Lock() + self.agent_id_lock = threading.Lock() + self.task_id_counter = 0 + self.agent_pool: dict[str, AgentBase] = {} + + def get_task_id(self) -> int: + """Get the auto-increment task id.""" + with self.task_id_lock: + self.task_id_counter += 1 + return self.task_id_counter + + def agent_exists(self, agent_id: str) -> bool: + """Check whether the agent exists. + + Args: + agent_id (`str`): the agent id. + + Returns: + bool: whether the agent exists. + """ + return agent_id in self.agent_pool + + def check_and_generate_agent( + self, + agent_id: str, + agent_configs: dict, + ) -> None: + """ + Check whether the agent exists, and create new agent instance + for new agent. + + Args: + agent_id (`str`): the agent id. + agent_configs (`dict`): configuration used to initialize the agent, + with three fields (generated in `_AgentMeta`): + + .. code-block:: python + + { + "class_name": {name of the agent} + "args": {args in tuple type to init the agent} + "kwargs": {args in dict type to init the agent} + } + + """ + with self.agent_id_lock: + if agent_id not in self.agent_pool: + agent_class_name = agent_configs["class_name"] + agent_instance = AgentBase.get_agent_class(agent_class_name)( + *agent_configs["args"], + **agent_configs["kwargs"], + ) + agent_instance._agent_id = agent_id # pylint: disable=W0212 + self.agent_pool[agent_id] = agent_instance + logger.info(f"create agent instance [{agent_id}]") + + def check_and_delete_agent(self, agent_id: str) -> None: + """ + Check whether the agent exists, and delete the agent instance + for the agent_id. + + Args: + agent_id (`str`): the agent id. + """ + with self.agent_id_lock: + if agent_id in self.agent_pool: + self.agent_pool.pop(agent_id) + logger.info(f"delete agent instance [{agent_id}]") + + def call_func( # pylint: disable=W0236 + self, + request: RpcMsg, + context: ServicerContext, + ) -> RpcMsg: + """Call the specific servicer function.""" + if hasattr(self, request.target_func): + if request.target_func not in ["_create_agent", "_get"]: + if not self.agent_exists(request.agent_id): + return context.abort( + grpc.StatusCode.INVALID_ARGUMENT, + f"Agent [{request.agent_id}] not exists.", + ) + return getattr(self, request.target_func)(request) + else: + # TODO: support other user defined method + logger.error(f"Unsupported method {request.target_func}") + return context.abort( + grpc.StatusCode.INVALID_ARGUMENT, + f"Unsupported method {request.target_func}", + ) + + def _reply(self, request: RpcMsg) -> RpcMsg: + """Call function of RpcAgentService + + Args: + request (`RpcMsg`): + Message containing input parameters or input parameter + placeholders. + + Returns: + `RpcMsg`: A serialized Msg instance with attributes name, host, + port and task_id + """ + if request.value: + msg = deserialize(request.value) + else: + msg = None + task_id = self.get_task_id() + self.result_pool[task_id] = threading.Condition() + self.executor.submit( + self.process_messages, + task_id, + request.agent_id, + msg, # type: ignore[arg-type] + ) + return RpcMsg( + value=Msg( # type: ignore[arg-type] + name=self.agent_pool[request.agent_id].name, + content=None, + task_id=task_id, + ).serialize(), + ) + + def _get(self, request: RpcMsg) -> RpcMsg: + """Get function of RpcAgentService + + Args: + request (`RpcMsg`): + Identifier of message, with json format:: + + { + 'task_id': int + } + + Returns: + `RpcMsg`: Concrete values of the specific message (or part of it). + """ + msg = json.loads(request.value) + while True: + result = self.result_pool.get(msg["task_id"]) + if isinstance(result, threading.Condition): + with result: + result.wait(timeout=1) + else: + break + return RpcMsg(value=result.serialize()) + + def _observe(self, request: RpcMsg) -> RpcMsg: + """Observe function of RpcAgentService + + Args: + request (`RpcMsg`): + The serialized input to be observed. + + Returns: + `RpcMsg`: Empty RpcMsg. + """ + msgs = deserialize(request.value) + for msg in msgs: + if isinstance(msg, PlaceholderMessage): + msg.update_value() + self.agent_pool[request.agent_id].observe(msgs) + return RpcMsg() + + def _create_agent(self, request: RpcMsg) -> RpcMsg: + """Create a new agent instance for the agent_id. + + Args: + request (RpcMsg): request message with a `agent_id` field. + """ + self.check_and_generate_agent( + request.agent_id, + agent_configs=( + dill.loads(base64.b64decode(request.value)) + if request.value + else None + ), + ) + return RpcMsg() + + def _clone_agent(self, request: RpcMsg) -> RpcMsg: + """Clone a new agent instance from the origin instance. + + Args: + request (RpcMsg): The `agent_id` field is the agent_id of the + agent to be cloned. + + Returns: + `RpcMsg`: The `value` field contains the agent_id of generated + agent. + """ + agent_id = request.agent_id + with self.agent_id_lock: + if agent_id not in self.agent_pool: + raise ValueError(f"Agent [{agent_id}] not exists") + ori_agent = self.agent_pool[agent_id] + new_agent = ori_agent.__class__( + *ori_agent._init_settings["args"], # pylint: disable=W0212 + **ori_agent._init_settings["kwargs"], # pylint: disable=W0212 + ) + with self.agent_id_lock: + self.agent_pool[new_agent.agent_id] = new_agent + return RpcMsg(value=new_agent.agent_id) # type: ignore[arg-type] + + def _delete_agent(self, request: RpcMsg) -> RpcMsg: + """Delete the agent instance of the specific sesssion_id. + + Args: + request (RpcMsg): request message with a `agent_id` field. + """ + self.check_and_delete_agent(request.agent_id) + return RpcMsg() + + def process_messages( + self, + task_id: int, + agent_id: str, + task_msg: dict = None, + ) -> None: + """Task processing.""" + if isinstance(task_msg, PlaceholderMessage): + task_msg.update_value() + cond = self.result_pool[task_id] + try: + result = self.agent_pool[agent_id].reply(task_msg) + self.result_pool[task_id] = result + except Exception: + error_msg = traceback.format_exc() + logger.error(f"Error in agent [{agent_id}]:\n{error_msg}") + self.result_pool[task_id] = Msg( + name="ERROR", + role="assistant", + __status="ERROR", + content=f"Error in agent [{agent_id}]:\n{error_msg}", + ) + with cond: + cond.notify_all() diff --git a/tests/rpc_agent_test.py b/tests/rpc_agent_test.py index d010587cd..d70613268 100644 --- a/tests/rpc_agent_test.py +++ b/tests/rpc_agent_test.py @@ -9,7 +9,7 @@ import agentscope from agentscope.agents import AgentBase, DistConf -from agentscope.agents.rpc_agent import RpcAgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg from agentscope.message import PlaceholderMessage from agentscope.message import deserialize From dbf5b18a7cf32c0b4d97ca8f8263d838124a0559 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 14:08:12 +0800 Subject: [PATCH 02/80] rename RpcAgentServerLauncher to AgentServerLauncher --- .../en/source/tutorial/208-distribute.md | 4 ++-- .../zh_CN/source/tutorial/208-distribute.md | 4 ++-- .../distributed_basic/distributed_dialog.py | 4 ++-- .../distributed_debate/distributed_debate.py | 4 ++-- examples/distributed_simulation/main.py | 4 ++-- src/agentscope/agents/agent.py | 4 ++-- src/agentscope/agents/rpc_agent.py | 4 ++-- src/agentscope/server/__init__.py | 4 ++-- src/agentscope/server/launcher.py | 20 +++++++++---------- tests/rpc_agent_test.py | 10 +++++----- 10 files changed, 31 insertions(+), 31 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 714f2e05f..5574276c4 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -69,7 +69,7 @@ agentscope.init( ... ) # Create an agent service process -server = RpcAgentServerLauncher( +server = AgentServerLauncher( host="ip_a", port=12001, # choose an available port ) @@ -88,7 +88,7 @@ agentscope.init( ... ) # Create an agent service process -server = RpcAgentServerLauncher( +server = AgentServerLauncher( host="ip_b", port=12002, # choose an available port ) diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index ef50f123f..3506e6641 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -68,7 +68,7 @@ agentscope.init( ... ) # Create an agent service process -server = RpcAgentServerLauncher( +server = AgentServerLauncher( host="ip_a", port=12001, # choose an available port ) @@ -87,7 +87,7 @@ agentscope.init( ... ) # Create an agent service process -server = RpcAgentServerLauncher( +server = AgentServerLauncher( host="ip_b", port=12002, # choose an available port ) diff --git a/examples/distributed_basic/distributed_dialog.py b/examples/distributed_basic/distributed_dialog.py index ab0de4235..c101fd2f6 100644 --- a/examples/distributed_basic/distributed_dialog.py +++ b/examples/distributed_basic/distributed_dialog.py @@ -7,7 +7,7 @@ import agentscope from agentscope.agents.user_agent import UserAgent from agentscope.agents.dialog_agent import DialogAgent -from agentscope.server import RpcAgentServerLauncher +from agentscope.server import AgentServerLauncher def parse_args() -> argparse.Namespace: @@ -36,7 +36,7 @@ def setup_assistant_server(assistant_host: str, assistant_port: int) -> None: agentscope.init( model_configs="configs/model_configs.json", ) - assistant_server_launcher = RpcAgentServerLauncher( + assistant_server_launcher = AgentServerLauncher( host=assistant_host, port=assistant_port, ) diff --git a/examples/distributed_debate/distributed_debate.py b/examples/distributed_debate/distributed_debate.py index a4e0a4287..7fc84024b 100644 --- a/examples/distributed_debate/distributed_debate.py +++ b/examples/distributed_debate/distributed_debate.py @@ -8,7 +8,7 @@ import agentscope from agentscope.agents import DialogAgent from agentscope.msghub import msghub -from agentscope.server import RpcAgentServerLauncher +from agentscope.server import AgentServerLauncher from agentscope.message import Msg from agentscope.utils.logging_utils import logger @@ -75,7 +75,7 @@ def setup_server(parsed_args: argparse.Namespace) -> None: ) host = getattr(parsed_args, f"{parsed_args.role}_host") port = getattr(parsed_args, f"{parsed_args.role}_port") - server_launcher = RpcAgentServerLauncher( + server_launcher = AgentServerLauncher( host=host, port=port, custom_agents=[UserProxyAgent, DialogAgent], diff --git a/examples/distributed_simulation/main.py b/examples/distributed_simulation/main.py index bb26fe533..130527d78 100644 --- a/examples/distributed_simulation/main.py +++ b/examples/distributed_simulation/main.py @@ -11,7 +11,7 @@ import agentscope from agentscope.agents import AgentBase -from agentscope.server import RpcAgentServerLauncher +from agentscope.server import AgentServerLauncher from agentscope.message import Msg @@ -58,7 +58,7 @@ def setup_participant_agent_server(host: str, port: int) -> None: model_configs="configs/model_configs.json", use_monitor=False, ) - assistant_server_launcher = RpcAgentServerLauncher( + assistant_server_launcher = AgentServerLauncher( host=host, port=port, max_pool_size=16384, diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index bdf657df2..91f45527a 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -24,7 +24,7 @@ class _AgentMeta(ABCMeta): """ def __init__(cls, name: Any, bases: Any, attrs: Any) -> None: - if not hasattr(cls, "registry"): + if not hasattr(cls, "_registry"): cls._registry = {} else: if name in cls._registry: @@ -245,7 +245,7 @@ def register_agent_class(cls, agent_class: Type[AgentBase]) -> None: """ agent_class_name = agent_class.__name__ if agent_class_name in cls._registry: - logger.warning( + logger.info( f"Agent class with name [{agent_class_name}] already exists.", ) else: diff --git a/src/agentscope/agents/rpc_agent.py b/src/agentscope/agents/rpc_agent.py index e4274f700..3332b00d2 100644 --- a/src/agentscope/agents/rpc_agent.py +++ b/src/agentscope/agents/rpc_agent.py @@ -9,7 +9,7 @@ serialize, ) from agentscope.rpc import RpcAgentClient -from agentscope.server.launcher import RpcAgentServerLauncher +from agentscope.server.launcher import AgentServerLauncher def rpc_servicer_method( # type: ignore[no-untyped-def] @@ -89,7 +89,7 @@ def __init__( launch_server = port is None if launch_server: self.host = "localhost" - self.server_launcher = RpcAgentServerLauncher( + self.server_launcher = AgentServerLauncher( host=self.host, port=port, max_pool_size=max_pool_size, diff --git a/src/agentscope/server/__init__.py b/src/agentscope/server/__init__.py index 18b2ad790..0ce9e8d3d 100644 --- a/src/agentscope/server/__init__.py +++ b/src/agentscope/server/__init__.py @@ -1,9 +1,9 @@ # -*- coding: utf-8 -*- """Import all server related modules in the package.""" -from .launcher import RpcAgentServerLauncher +from .launcher import AgentServerLauncher from .servicer import AgentPlatform __all__ = [ - "RpcAgentServerLauncher", + "AgentServerLauncher", "AgentPlatform", ] diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index 387552115..78afb4f0f 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -24,7 +24,7 @@ add_RpcAgentServicer_to_server = Any -def setup_rpc_agent_server( +def setup_agent_server( host: str, port: int, init_settings: dict = None, @@ -36,7 +36,7 @@ def setup_rpc_agent_server( max_timeout_seconds: int = 1800, custom_agents: list = None, ) -> None: - """Setup gRPC server rpc agent. + """Setup agent server. Args: host (`str`, defaults to `"localhost"`): @@ -63,7 +63,7 @@ def setup_rpc_agent_server( A list of custom agent classes that are not in `agentscope.agents`. """ asyncio.run( - setup_rpc_agent_server_async( + setup_agent_server_async( host=host, port=port, init_settings=init_settings, @@ -78,7 +78,7 @@ def setup_rpc_agent_server( ) -async def setup_rpc_agent_server_async( +async def setup_agent_server_async( host: str, port: int, init_settings: dict = None, @@ -90,7 +90,7 @@ async def setup_rpc_agent_server_async( max_timeout_seconds: int = 1800, custom_agents: list = None, ) -> None: - """Setup gRPC server rpc agent in an async way. + """Setup agent server in an async way. Args: host (`str`, defaults to `"localhost"`): @@ -207,7 +207,7 @@ def check_port(port: Optional[int] = None) -> int: return port -class RpcAgentServerLauncher: +class AgentServerLauncher: """The launcher of AgentPlatform (formerly RpcAgentServer).""" def __init__( @@ -222,7 +222,7 @@ def __init__( agent_args: tuple = (), agent_kwargs: dict = None, ) -> None: - """Init a rpc agent server launcher. + """Init a launcher of agent server. Args: host (`str`, defaults to `"localhost"`): @@ -262,7 +262,7 @@ def __init__( ): logger.warning( "`agent_class`, `agent_args` and `agent_kwargs` is deprecated" - " in `RpcAgentServerLauncher`", + " in `AgentServerLauncher`", ) def _launch_in_main(self) -> None: @@ -271,7 +271,7 @@ def _launch_in_main(self) -> None: f"Launching agent server at [{self.host}:{self.port}]...", ) asyncio.run( - setup_rpc_agent_server_async( + setup_agent_server_async( host=self.host, port=self.port, max_pool_size=self.max_pool_size, @@ -289,7 +289,7 @@ def _launch_in_sub(self) -> None: self.parent_con, child_con = Pipe() start_event = Event() server_process = Process( - target=setup_rpc_agent_server, + target=setup_agent_server, kwargs={ "host": self.host, "port": self.port, diff --git a/tests/rpc_agent_test.py b/tests/rpc_agent_test.py index d70613268..bc861824f 100644 --- a/tests/rpc_agent_test.py +++ b/tests/rpc_agent_test.py @@ -9,7 +9,7 @@ import agentscope from agentscope.agents import AgentBase, DistConf -from agentscope.server import RpcAgentServerLauncher +from agentscope.server import AgentServerLauncher from agentscope.message import Msg from agentscope.message import PlaceholderMessage from agentscope.message import deserialize @@ -220,7 +220,7 @@ def test_single_rpc_agent_server(self) -> None: def test_connect_to_an_existing_rpc_server(self) -> None: """test connecting to an existing server""" - launcher = RpcAgentServerLauncher( + launcher = AgentServerLauncher( # choose port automatically host="127.0.0.1", port=12010, @@ -420,7 +420,7 @@ def test_standalone_multiprocess_init(self) -> None: def test_multi_agent_in_same_server(self) -> None: """test agent server with multi agent""" - launcher = RpcAgentServerLauncher( + launcher = AgentServerLauncher( host="127.0.0.1", port=12010, local_mode=False, @@ -548,14 +548,14 @@ def test_error_handling(self) -> None: def test_agent_nesting(self) -> None: """Test agent nesting""" host = "localhost" - launcher1 = RpcAgentServerLauncher( + launcher1 = AgentServerLauncher( # choose port automatically host=host, port=12010, local_mode=False, custom_agents=[DemoGatherAgent, DemoGeneratorAgent], ) - launcher2 = RpcAgentServerLauncher( + launcher2 = AgentServerLauncher( # choose port automatically host=host, port=12011, From 7997f7a83641f2b93851866a916e998bf73d82c7 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 15:04:13 +0800 Subject: [PATCH 03/80] add as_server into setup and support graceful shutdown --- setup.py | 1 + src/agentscope/server/launcher.py | 73 ++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index 7dca2181b..955bb47d2 100644 --- a/setup.py +++ b/setup.py @@ -121,6 +121,7 @@ "console_scripts": [ "as_studio=agentscope.web.studio.studio:run_app", "as_workflow=agentscope.web.workstation.workflow:main", + "as_server=agentscope.server.launcher:launch", ], }, ) diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index 78afb4f0f..6328fe865 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -4,6 +4,8 @@ from multiprocessing.synchronize import Event as EventClass import socket import asyncio +import signal +import argparse from typing import Any, Type, Optional from concurrent import futures from loguru import logger @@ -130,13 +132,24 @@ async def setup_agent_server_async( if custom_agents is not None: for agent_class in custom_agents: AgentBase.register_agent_class(agent_class=agent_class) + + async def shutdown_signal_handler() -> None: + logger.info( + f"Received shutdown signal. Gracefully stopping the server at " + f"[{host}:{port}].", + ) + await server.stop(grace=5) + + loop = asyncio.get_running_loop() + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler( + sig, + lambda: asyncio.create_task(shutdown_signal_handler()), + ) while True: try: port = check_port(port) servicer.port = port - logger.info( - f"Starting rpc server at port [{port}]...", - ) server = grpc.aio.server( futures.ThreadPoolExecutor(max_workers=None), ) @@ -149,11 +162,11 @@ async def setup_agent_server_async( break except OSError: logger.warning( - f"Failed to start rpc server at port [{port}]" + f"Failed to start agent server at port [{port}]" f"try another port", ) logger.info( - f"rpc server at port [{port}] started successfully", + f"agent server at [{host}:{port}] started successfully", ) if start_event is not None: pipe.send(port) @@ -161,13 +174,13 @@ async def setup_agent_server_async( while not stop_event.is_set(): await asyncio.sleep(1) logger.info( - f"Stopping rpc server at port [{port}]", + f"Stopping agent server at [{host}:{port}]", ) await server.stop(10.0) else: await server.wait_for_termination() logger.info( - f"rpc server at port [{port}] stopped successfully", + f"agent server at [{host}:{port}] stopped successfully", ) @@ -342,3 +355,49 @@ def shutdown(self) -> None: f"Agent server at port [{self.port}] is killed.", ) self.server = None + + +def launch() -> None: + """Launch an agent server""" + + parser = argparse.ArgumentParser() + parser.add_argument( + "--host", + type=str, + default="localhost", + help="hostname of the server", + ) + parser.add_argument( + "--port", + type=int, + default=12310, + help="socket port of the server", + ) + parser.add_argument( + "--max_pool_size", + type=int, + default=8192, + help="max number of task results that the server can accommodate", + ) + parser.add_argument( + "--max_timeout_seconds", + type=int, + default=1800, + help="max timeout for task results", + ) + parser.add_argument( + "--local_mode", + type=bool, + default=False, + help="whether the started rpc server only listens to local requests", + ) + args = parser.parse_args() + launcher = AgentServerLauncher( + host=args.host, + port=args.port, + max_pool_size=args.max_pool_size, + max_timeout_seconds=args.max_timeout_seconds, + local_mode=args.local_mode, + ) + launcher.launch(in_subprocess=False) + launcher.wait_until_terminate() From a92eac2959baff923d3df7fc36e9c6f800fe7766 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 15:37:57 +0800 Subject: [PATCH 04/80] add server_id --- src/agentscope/server/launcher.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index 6328fe865..f2e2be15b 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -17,6 +17,7 @@ from .servicer import AgentPlatform from ..agents.agent import AgentBase +from ..utils.tools import _get_timestamp try: from ..rpc.rpc_agent_pb2_grpc import ( @@ -29,6 +30,7 @@ def setup_agent_server( host: str, port: int, + server_id: str, init_settings: dict = None, start_event: EventClass = None, stop_event: EventClass = None, @@ -45,6 +47,8 @@ def setup_agent_server( Hostname of the rpc agent server. port (`int`): The socket port monitored by grpc server. + server_id (`str`): + The id of the server. init_settings (`dict`, defaults to `None`): Init settings for agentscope.init. start_event (`EventClass`, defaults to `None`): @@ -68,6 +72,7 @@ def setup_agent_server( setup_agent_server_async( host=host, port=port, + server_id=server_id, init_settings=init_settings, start_event=start_event, stop_event=stop_event, @@ -83,6 +88,7 @@ def setup_agent_server( async def setup_agent_server_async( host: str, port: int, + server_id: str, init_settings: dict = None, start_event: EventClass = None, stop_event: EventClass = None, @@ -99,6 +105,8 @@ async def setup_agent_server_async( Hostname of the rpc agent server. port (`int`): The socket port monitored by grpc server. + server_id (`str`): + The id of the server. init_settings (`dict`, defaults to `None`): Init settings for agentscope.init. start_event (`EventClass`, defaults to `None`): @@ -166,7 +174,7 @@ async def shutdown_signal_handler() -> None: f"try another port", ) logger.info( - f"agent server at [{host}:{port}] started successfully", + f"agent server [{server_id}] at {host}:{port} started successfully", ) if start_event is not None: pipe.send(port) @@ -180,7 +188,7 @@ async def shutdown_signal_handler() -> None: else: await server.wait_for_termination() logger.info( - f"agent server at [{host}:{port}] stopped successfully", + f"agent server [{server_id}] at {host}:{port} stopped successfully", ) @@ -231,6 +239,7 @@ def __init__( max_timeout_seconds: int = 1800, local_mode: bool = False, custom_agents: list = None, + server_id: str = None, agent_class: Type[AgentBase] = None, agent_args: tuple = (), agent_kwargs: dict = None, @@ -252,6 +261,9 @@ def __init__( custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in `agentscope.agents`. + server_id (`str`, defaults to `None`): + The id of the agent server. If not specified, a random id + will be generated. agent_class (`Type[AgentBase]`, deprecated): The AgentBase subclass encapsulated by this wrapper. agent_args (`tuple`, deprecated): The args tuple used to @@ -268,6 +280,9 @@ def __init__( self.stop_event = None self.parent_con = None self.custom_agents = custom_agents + self.server_id = ( + self.generate_server_id() if server_id is None else server_id + ) if ( agent_class is not None or len(agent_args) > 0 @@ -278,6 +293,10 @@ def __init__( " in `AgentServerLauncher`", ) + def generate_server_id(self) -> str: + """Generate server id""" + return f"{self.host}:{self.port}-{_get_timestamp('%y%m%d-%H:%M:%S')}" + def _launch_in_main(self) -> None: """Launch gRPC server in main-process""" logger.info( @@ -287,6 +306,7 @@ def _launch_in_main(self) -> None: setup_agent_server_async( host=self.host, port=self.port, + server_id=self.server_id, max_pool_size=self.max_pool_size, max_timeout_seconds=self.max_timeout_seconds, local_mode=self.local_mode, @@ -306,6 +326,7 @@ def _launch_in_sub(self) -> None: kwargs={ "host": self.host, "port": self.port, + "server_id": self.server_id, "init_settings": _INIT_SETTINGS, "start_event": start_event, "stop_event": self.stop_event, From 9163241265c42ef4eec513f4f28e96169f032d20 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 16:17:12 +0800 Subject: [PATCH 05/80] update tutorial for agent server --- docs/sphinx_doc/en/source/index.rst | 1 + .../en/source/tutorial/208-distribute.md | 15 ++++++ .../zh_CN/source/tutorial/208-distribute.md | 15 ++++++ setup.py | 2 +- src/agentscope/server/__init__.py | 3 +- src/agentscope/server/launcher.py | 50 +++++++++++++------ 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/docs/sphinx_doc/en/source/index.rst b/docs/sphinx_doc/en/source/index.rst index fb81e2e64..1aad67356 100644 --- a/docs/sphinx_doc/en/source/index.rst +++ b/docs/sphinx_doc/en/source/index.rst @@ -38,6 +38,7 @@ AgentScope Documentation agentscope.pipelines agentscope.service agentscope.rpc + agentscope.server agentscope.web agentscope.prompt agentscope.utils diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 5574276c4..24fbc3acf 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -79,6 +79,12 @@ server.launch() server.wait_until_terminate() ``` +> For similarity, you can run the following command in your terminal rather than the above code: +> +> ```shell +> as_server --host ip_a --port 12001 +> ``` + And run the following code on `Machine2`: ```python @@ -98,6 +104,12 @@ server.launch() server.wait_until_terminate() ``` +> Similarly, you can run the following command in your terminal to setup the agent server: +> +> ```shell +> as_server --host ip_b --port 12002 +> ``` + Then, you can connect to the agent servers from the main process with the following code. ```python @@ -254,6 +266,9 @@ About more detailed technical implementation solutions, please refer to our [pap In agentscope, the agent server provides a running platform for various types of agents. Multiple agents can run in the same agent server and hold independent memory and other local states but they will share the same computation resources. + +After installing the distributed version of AgentScope, you can use the `as_server` command to start the agent server, and the detailed startup arguments can be found in the documentation of the {func}`as_server` function. + As long as the code is not modified, an agent server can provide services for multiple main processes. This means that when running mutliple applications, you only need to start the agent server for the first time, and it can be reused subsequently. diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index 3506e6641..fe840aa00 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -78,6 +78,12 @@ server.launch() server.wait_until_terminate() ``` +> 为了进一步简化使用,可以在命令行中输入如下指令来代替上述代码: +> +> ```shell +> as_server --host ip_a --port 12001 +> ``` + 之后在 `Machine2` 上运行如下代码: ```python @@ -97,6 +103,12 @@ server.launch() server.wait_until_terminate() ``` +> 这里也同样可以用如下指令来代替上面的代码。 +> +> ```shell +> as_server --host ip_b --port 12002 +> ``` + 接下来,就可以使用如下代码从主进程中连接这两个智能体服务器进程。 ```python @@ -251,6 +263,9 @@ Placeholder 内部包含了该消息产生方的联络方法,可以通过网 #### Agent Server Agent Server 也就是智能体服务器。在 AgentScope 中,Agent Server 提供了一个让不同 Agent 实例运行的平台。多个不同类型的 Agent 可以运行在同一个 Agent Server 中并保持独立的记忆以及其他本地状态信息,但是他们将共享同一份计算资源。 + +在安装 AgentScope 的分布式版本后就可以通过 `as_server` 命令来启动 Agent Server,具体的启动参数在 {func}`as_server` 函数文档中可以找到。 + 只要没有对代码进行修改,一个已经启动的 Agent Server 可以为多个主流程提供服务。 这意味着在运行多个应用时,只需要在第一次运行前启动 Agent Server,后续这些 Agent Server 进程就可以持续复用。 diff --git a/setup.py b/setup.py index 955bb47d2..60e9c9ddd 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ "console_scripts": [ "as_studio=agentscope.web.studio.studio:run_app", "as_workflow=agentscope.web.workstation.workflow:main", - "as_server=agentscope.server.launcher:launch", + "as_server=agentscope.server.launcher:as_server", ], }, ) diff --git a/src/agentscope/server/__init__.py b/src/agentscope/server/__init__.py index 0ce9e8d3d..9dbfe3faa 100644 --- a/src/agentscope/server/__init__.py +++ b/src/agentscope/server/__init__.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- """Import all server related modules in the package.""" -from .launcher import AgentServerLauncher +from .launcher import AgentServerLauncher, as_server from .servicer import AgentPlatform __all__ = [ "AgentServerLauncher", "AgentPlatform", + "as_server", ] diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index f2e2be15b..39d30bf92 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -44,9 +44,9 @@ def setup_agent_server( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`): - The socket port monitored by grpc server. + The socket port monitored by the agent server. server_id (`str`): The id of the server. init_settings (`dict`, defaults to `None`): @@ -102,9 +102,9 @@ async def setup_agent_server_async( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`): - The socket port monitored by grpc server. + The socket port monitored by the agent server. server_id (`str`): The id of the server. init_settings (`dict`, defaults to `None`): @@ -214,7 +214,7 @@ def check_port(port: Optional[int] = None) -> int: if port is None: new_port = find_available_port() logger.warning( - "gRpc server port is not provided, automatically select " + "agent server port is not provided, automatically select " f"[{new_port}] as the port number.", ) return new_port @@ -248,15 +248,15 @@ def __init__( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`, defaults to `None`): - Port of the rpc agent server. + Socket port of the agent server. max_pool_size (`int`, defaults to `8192`): Max number of task results that the server can accommodate. max_timeout_seconds (`int`, defaults to `1800`): Timeout for task results. local_mode (`bool`, defaults to `False`): - Whether the started rpc server only listens to local + Whether the started server only listens to local requests. custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in @@ -298,7 +298,7 @@ def generate_server_id(self) -> str: return f"{self.host}:{self.port}-{_get_timestamp('%y%m%d-%H:%M:%S')}" def _launch_in_main(self) -> None: - """Launch gRPC server in main-process""" + """Launch agent server in main-process""" logger.info( f"Launching agent server at [{self.host}:{self.port}]...", ) @@ -315,7 +315,7 @@ def _launch_in_main(self) -> None: ) def _launch_in_sub(self) -> None: - """Launch gRPC server in sub-process.""" + """Launch an agent server in sub-process.""" from agentscope._init import _INIT_SETTINGS self.stop_event = Event() @@ -346,7 +346,7 @@ def _launch_in_sub(self) -> None: ) def launch(self, in_subprocess: bool = True) -> None: - """launch a rpc agent server. + """launch an agent server. Args: in_subprocess (bool, optional): launch the server in subprocess. @@ -364,7 +364,7 @@ def wait_until_terminate(self) -> None: self.server.join() def shutdown(self) -> None: - """Shutdown the rpc agent server.""" + """Shutdown the agent server.""" if self.server is not None: if self.stop_event is not None: self.stop_event.set() @@ -378,8 +378,28 @@ def shutdown(self) -> None: self.server = None -def launch() -> None: - """Launch an agent server""" +def as_server() -> None: + """Launch an agent server with terminal command. + + Note: + + The arguments of `as_server` are listed as follows: + + * `\-\-host`: the hostname of the server. + * `\-\-port`: the socket port of the server. + * `\-\-max_pool_size`: max number of task results that the server can + accommodate. + * `\-\-max_timeout_seconds`: max timeout seconds of a task. + * `\-\-local_mode`: whether the started agent server only listens to + local requests. + + In most cases, you only need to specify the `\-\-host` and `\-\-port`. + + .. code-block:: shell + + as_server --host localhost --port 12345 + + """ parser = argparse.ArgumentParser() parser.add_argument( @@ -410,7 +430,7 @@ def launch() -> None: "--local_mode", type=bool, default=False, - help="whether the started rpc server only listens to local requests", + help="whether the started agent server only listens to local requests", ) args = parser.parse_args() launcher = AgentServerLauncher( From 31228b64b3eff70bb9e08f948fab2cf3bb4f3eb0 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 16:17:12 +0800 Subject: [PATCH 06/80] update tutorial for agent server --- docs/sphinx_doc/en/source/index.rst | 1 + .../en/source/tutorial/208-distribute.md | 15 ++++++ .../zh_CN/source/tutorial/208-distribute.md | 15 ++++++ setup.py | 2 +- src/agentscope/server/__init__.py | 3 +- src/agentscope/server/launcher.py | 50 +++++++++++++------ 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/docs/sphinx_doc/en/source/index.rst b/docs/sphinx_doc/en/source/index.rst index fb81e2e64..1aad67356 100644 --- a/docs/sphinx_doc/en/source/index.rst +++ b/docs/sphinx_doc/en/source/index.rst @@ -38,6 +38,7 @@ AgentScope Documentation agentscope.pipelines agentscope.service agentscope.rpc + agentscope.server agentscope.web agentscope.prompt agentscope.utils diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 5574276c4..24fbc3acf 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -79,6 +79,12 @@ server.launch() server.wait_until_terminate() ``` +> For similarity, you can run the following command in your terminal rather than the above code: +> +> ```shell +> as_server --host ip_a --port 12001 +> ``` + And run the following code on `Machine2`: ```python @@ -98,6 +104,12 @@ server.launch() server.wait_until_terminate() ``` +> Similarly, you can run the following command in your terminal to setup the agent server: +> +> ```shell +> as_server --host ip_b --port 12002 +> ``` + Then, you can connect to the agent servers from the main process with the following code. ```python @@ -254,6 +266,9 @@ About more detailed technical implementation solutions, please refer to our [pap In agentscope, the agent server provides a running platform for various types of agents. Multiple agents can run in the same agent server and hold independent memory and other local states but they will share the same computation resources. + +After installing the distributed version of AgentScope, you can use the `as_server` command to start the agent server, and the detailed startup arguments can be found in the documentation of the {func}`as_server` function. + As long as the code is not modified, an agent server can provide services for multiple main processes. This means that when running mutliple applications, you only need to start the agent server for the first time, and it can be reused subsequently. diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index 3506e6641..fe840aa00 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -78,6 +78,12 @@ server.launch() server.wait_until_terminate() ``` +> 为了进一步简化使用,可以在命令行中输入如下指令来代替上述代码: +> +> ```shell +> as_server --host ip_a --port 12001 +> ``` + 之后在 `Machine2` 上运行如下代码: ```python @@ -97,6 +103,12 @@ server.launch() server.wait_until_terminate() ``` +> 这里也同样可以用如下指令来代替上面的代码。 +> +> ```shell +> as_server --host ip_b --port 12002 +> ``` + 接下来,就可以使用如下代码从主进程中连接这两个智能体服务器进程。 ```python @@ -251,6 +263,9 @@ Placeholder 内部包含了该消息产生方的联络方法,可以通过网 #### Agent Server Agent Server 也就是智能体服务器。在 AgentScope 中,Agent Server 提供了一个让不同 Agent 实例运行的平台。多个不同类型的 Agent 可以运行在同一个 Agent Server 中并保持独立的记忆以及其他本地状态信息,但是他们将共享同一份计算资源。 + +在安装 AgentScope 的分布式版本后就可以通过 `as_server` 命令来启动 Agent Server,具体的启动参数在 {func}`as_server` 函数文档中可以找到。 + 只要没有对代码进行修改,一个已经启动的 Agent Server 可以为多个主流程提供服务。 这意味着在运行多个应用时,只需要在第一次运行前启动 Agent Server,后续这些 Agent Server 进程就可以持续复用。 diff --git a/setup.py b/setup.py index 955bb47d2..60e9c9ddd 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ "console_scripts": [ "as_studio=agentscope.web.studio.studio:run_app", "as_workflow=agentscope.web.workstation.workflow:main", - "as_server=agentscope.server.launcher:launch", + "as_server=agentscope.server.launcher:as_server", ], }, ) diff --git a/src/agentscope/server/__init__.py b/src/agentscope/server/__init__.py index 0ce9e8d3d..9dbfe3faa 100644 --- a/src/agentscope/server/__init__.py +++ b/src/agentscope/server/__init__.py @@ -1,9 +1,10 @@ # -*- coding: utf-8 -*- """Import all server related modules in the package.""" -from .launcher import AgentServerLauncher +from .launcher import AgentServerLauncher, as_server from .servicer import AgentPlatform __all__ = [ "AgentServerLauncher", "AgentPlatform", + "as_server", ] diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index f2e2be15b..648c4b2a5 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -44,9 +44,9 @@ def setup_agent_server( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`): - The socket port monitored by grpc server. + The socket port monitored by the agent server. server_id (`str`): The id of the server. init_settings (`dict`, defaults to `None`): @@ -102,9 +102,9 @@ async def setup_agent_server_async( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`): - The socket port monitored by grpc server. + The socket port monitored by the agent server. server_id (`str`): The id of the server. init_settings (`dict`, defaults to `None`): @@ -214,7 +214,7 @@ def check_port(port: Optional[int] = None) -> int: if port is None: new_port = find_available_port() logger.warning( - "gRpc server port is not provided, automatically select " + "agent server port is not provided, automatically select " f"[{new_port}] as the port number.", ) return new_port @@ -248,15 +248,15 @@ def __init__( Args: host (`str`, defaults to `"localhost"`): - Hostname of the rpc agent server. + Hostname of the agent server. port (`int`, defaults to `None`): - Port of the rpc agent server. + Socket port of the agent server. max_pool_size (`int`, defaults to `8192`): Max number of task results that the server can accommodate. max_timeout_seconds (`int`, defaults to `1800`): Timeout for task results. local_mode (`bool`, defaults to `False`): - Whether the started rpc server only listens to local + Whether the started server only listens to local requests. custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in @@ -298,7 +298,7 @@ def generate_server_id(self) -> str: return f"{self.host}:{self.port}-{_get_timestamp('%y%m%d-%H:%M:%S')}" def _launch_in_main(self) -> None: - """Launch gRPC server in main-process""" + """Launch agent server in main-process""" logger.info( f"Launching agent server at [{self.host}:{self.port}]...", ) @@ -315,7 +315,7 @@ def _launch_in_main(self) -> None: ) def _launch_in_sub(self) -> None: - """Launch gRPC server in sub-process.""" + """Launch an agent server in sub-process.""" from agentscope._init import _INIT_SETTINGS self.stop_event = Event() @@ -346,7 +346,7 @@ def _launch_in_sub(self) -> None: ) def launch(self, in_subprocess: bool = True) -> None: - """launch a rpc agent server. + """launch an agent server. Args: in_subprocess (bool, optional): launch the server in subprocess. @@ -364,7 +364,7 @@ def wait_until_terminate(self) -> None: self.server.join() def shutdown(self) -> None: - """Shutdown the rpc agent server.""" + """Shutdown the agent server.""" if self.server is not None: if self.stop_event is not None: self.stop_event.set() @@ -378,8 +378,28 @@ def shutdown(self) -> None: self.server = None -def launch() -> None: - """Launch an agent server""" +def as_server() -> None: + """Launch an agent server with terminal command. + + Note: + + The arguments of `as_server` are listed as follows: + + * `--host`: the hostname of the server. + * `--port`: the socket port of the server. + * `--max_pool_size`: max number of task results that the server can + accommodate. + * `--max_timeout_seconds`: max timeout seconds of a task. + * `--local_mode`: whether the started agent server only listens to + local requests. + + In most cases, you only need to specify the `--host` and `--port`. + + .. code-block:: shell + + as_server --host localhost --port 12345 + + """ # noqa parser = argparse.ArgumentParser() parser.add_argument( @@ -410,7 +430,7 @@ def launch() -> None: "--local_mode", type=bool, default=False, - help="whether the started rpc server only listens to local requests", + help="whether the started agent server only listens to local requests", ) args = parser.parse_args() launcher = AgentServerLauncher( From 918c75097ea6bf0afb55e63f293287ac424228ff Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 16:45:53 +0800 Subject: [PATCH 07/80] fix windows add_signal_handler --- src/agentscope/server/launcher.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index 272dacd34..f7feb0c34 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- """ Server of distributed agent""" +import os from multiprocessing import Process, Event, Pipe from multiprocessing.synchronize import Event as EventClass import socket @@ -149,11 +150,13 @@ async def shutdown_signal_handler() -> None: await server.stop(grace=5) loop = asyncio.get_running_loop() - for sig in (signal.SIGINT, signal.SIGTERM): - loop.add_signal_handler( - sig, - lambda: asyncio.create_task(shutdown_signal_handler()), - ) + if os.name != "nt": + # windows does not support add_signal_handler + for sig in (signal.SIGINT, signal.SIGTERM): + loop.add_signal_handler( + sig, + lambda: asyncio.create_task(shutdown_signal_handler()), + ) while True: try: port = check_port(port) @@ -184,7 +187,7 @@ async def shutdown_signal_handler() -> None: logger.info( f"Stopping agent server at [{host}:{port}]", ) - await server.stop(10.0) + await server.stop(grace=10.0) else: await server.wait_for_termination() logger.info( From 8f3eaa29a96b7bb05c2ee2eae7cd95a7996ee718 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 17:32:08 +0800 Subject: [PATCH 08/80] rename agentserverlauncher --- docs/sphinx_doc/en/source/tutorial/208-distribute.md | 4 ++-- docs/sphinx_doc/zh_CN/source/index.rst | 1 + .../zh_CN/source/tutorial/208-distribute.md | 4 ++-- examples/distributed_basic/distributed_dialog.py | 4 ++-- examples/distributed_debate/distributed_debate.py | 4 ++-- examples/distributed_simulation/main.py | 4 ++-- src/agentscope/agents/rpc_agent.py | 4 ++-- src/agentscope/server/__init__.py | 8 ++++---- src/agentscope/server/launcher.py | 12 ++++++------ src/agentscope/server/servicer.py | 6 +++--- tests/rpc_agent_test.py | 10 +++++----- 11 files changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 24fbc3acf..e5a16d977 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -69,7 +69,7 @@ agentscope.init( ... ) # Create an agent service process -server = AgentServerLauncher( +server = RpcAgentServerLauncher( host="ip_a", port=12001, # choose an available port ) @@ -94,7 +94,7 @@ agentscope.init( ... ) # Create an agent service process -server = AgentServerLauncher( +server = RpcAgentServerLauncher( host="ip_b", port=12002, # choose an available port ) diff --git a/docs/sphinx_doc/zh_CN/source/index.rst b/docs/sphinx_doc/zh_CN/source/index.rst index 7f6c48275..662fb267c 100644 --- a/docs/sphinx_doc/zh_CN/source/index.rst +++ b/docs/sphinx_doc/zh_CN/source/index.rst @@ -38,6 +38,7 @@ AgentScope 文档 agentscope.pipelines agentscope.service agentscope.rpc + agentscope.server agentscope.web agentscope.prompt agentscope.utils diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index fe840aa00..ab4f24520 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -68,7 +68,7 @@ agentscope.init( ... ) # Create an agent service process -server = AgentServerLauncher( +server = RpcAgentServerLauncher( host="ip_a", port=12001, # choose an available port ) @@ -93,7 +93,7 @@ agentscope.init( ... ) # Create an agent service process -server = AgentServerLauncher( +server = RpcAgentServerLauncher( host="ip_b", port=12002, # choose an available port ) diff --git a/examples/distributed_basic/distributed_dialog.py b/examples/distributed_basic/distributed_dialog.py index c101fd2f6..ab0de4235 100644 --- a/examples/distributed_basic/distributed_dialog.py +++ b/examples/distributed_basic/distributed_dialog.py @@ -7,7 +7,7 @@ import agentscope from agentscope.agents.user_agent import UserAgent from agentscope.agents.dialog_agent import DialogAgent -from agentscope.server import AgentServerLauncher +from agentscope.server import RpcAgentServerLauncher def parse_args() -> argparse.Namespace: @@ -36,7 +36,7 @@ def setup_assistant_server(assistant_host: str, assistant_port: int) -> None: agentscope.init( model_configs="configs/model_configs.json", ) - assistant_server_launcher = AgentServerLauncher( + assistant_server_launcher = RpcAgentServerLauncher( host=assistant_host, port=assistant_port, ) diff --git a/examples/distributed_debate/distributed_debate.py b/examples/distributed_debate/distributed_debate.py index 7fc84024b..a4e0a4287 100644 --- a/examples/distributed_debate/distributed_debate.py +++ b/examples/distributed_debate/distributed_debate.py @@ -8,7 +8,7 @@ import agentscope from agentscope.agents import DialogAgent from agentscope.msghub import msghub -from agentscope.server import AgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg from agentscope.utils.logging_utils import logger @@ -75,7 +75,7 @@ def setup_server(parsed_args: argparse.Namespace) -> None: ) host = getattr(parsed_args, f"{parsed_args.role}_host") port = getattr(parsed_args, f"{parsed_args.role}_port") - server_launcher = AgentServerLauncher( + server_launcher = RpcAgentServerLauncher( host=host, port=port, custom_agents=[UserProxyAgent, DialogAgent], diff --git a/examples/distributed_simulation/main.py b/examples/distributed_simulation/main.py index 130527d78..bb26fe533 100644 --- a/examples/distributed_simulation/main.py +++ b/examples/distributed_simulation/main.py @@ -11,7 +11,7 @@ import agentscope from agentscope.agents import AgentBase -from agentscope.server import AgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg @@ -58,7 +58,7 @@ def setup_participant_agent_server(host: str, port: int) -> None: model_configs="configs/model_configs.json", use_monitor=False, ) - assistant_server_launcher = AgentServerLauncher( + assistant_server_launcher = RpcAgentServerLauncher( host=host, port=port, max_pool_size=16384, diff --git a/src/agentscope/agents/rpc_agent.py b/src/agentscope/agents/rpc_agent.py index 3332b00d2..e4274f700 100644 --- a/src/agentscope/agents/rpc_agent.py +++ b/src/agentscope/agents/rpc_agent.py @@ -9,7 +9,7 @@ serialize, ) from agentscope.rpc import RpcAgentClient -from agentscope.server.launcher import AgentServerLauncher +from agentscope.server.launcher import RpcAgentServerLauncher def rpc_servicer_method( # type: ignore[no-untyped-def] @@ -89,7 +89,7 @@ def __init__( launch_server = port is None if launch_server: self.host = "localhost" - self.server_launcher = AgentServerLauncher( + self.server_launcher = RpcAgentServerLauncher( host=self.host, port=port, max_pool_size=max_pool_size, diff --git a/src/agentscope/server/__init__.py b/src/agentscope/server/__init__.py index 9dbfe3faa..8b69a542a 100644 --- a/src/agentscope/server/__init__.py +++ b/src/agentscope/server/__init__.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- """Import all server related modules in the package.""" -from .launcher import AgentServerLauncher, as_server -from .servicer import AgentPlatform +from .launcher import RpcAgentServerLauncher, as_server +from .servicer import AgentServerServicer __all__ = [ - "AgentServerLauncher", - "AgentPlatform", + "RpcAgentServerLauncher", + "AgentServerServicer", "as_server", ] diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index f7feb0c34..34c09f1e7 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -16,7 +16,7 @@ except ImportError: grpc = None -from .servicer import AgentPlatform +from .servicer import AgentServerServicer from ..agents.agent import AgentBase from ..utils.tools import _get_timestamp @@ -131,7 +131,7 @@ async def setup_agent_server_async( if init_settings is not None: init_process(**init_settings) - servicer = AgentPlatform( + servicer = AgentServerServicer( host=host, port=port, max_pool_size=max_pool_size, @@ -231,8 +231,8 @@ def check_port(port: Optional[int] = None) -> int: return port -class AgentServerLauncher: - """The launcher of AgentPlatform (formerly RpcAgentServer).""" +class RpcAgentServerLauncher: + """The launcher of AgentServer.""" def __init__( self, @@ -293,7 +293,7 @@ def __init__( ): logger.warning( "`agent_class`, `agent_args` and `agent_kwargs` is deprecated" - " in `AgentServerLauncher`", + " in `RpcAgentServerLauncher`", ) def generate_server_id(self) -> str: @@ -436,7 +436,7 @@ def as_server() -> None: help="whether the started agent server only listens to local requests", ) args = parser.parse_args() - launcher = AgentServerLauncher( + launcher = RpcAgentServerLauncher( host=args.host, port=args.port, max_pool_size=args.max_pool_size, diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py index b178720b5..055e8d7ab 100644 --- a/src/agentscope/server/servicer.py +++ b/src/agentscope/server/servicer.py @@ -34,8 +34,8 @@ RpcAgentServicer = Any -class AgentPlatform(RpcAgentServicer): - """A platform for agent to run on (formerly RpcServerSideWrapper)""" +class AgentServerServicer(RpcAgentServicer): + """A Servicer for agent to run on (formerly RpcServerSideWrapper)""" def __init__( self, @@ -44,7 +44,7 @@ def __init__( max_pool_size: int = 8192, max_timeout_seconds: int = 1800, ): - """Init the AgentPlatform. + """Init the AgentServerServicer. Args: host (`str`, defaults to "localhost"): diff --git a/tests/rpc_agent_test.py b/tests/rpc_agent_test.py index bc861824f..d70613268 100644 --- a/tests/rpc_agent_test.py +++ b/tests/rpc_agent_test.py @@ -9,7 +9,7 @@ import agentscope from agentscope.agents import AgentBase, DistConf -from agentscope.server import AgentServerLauncher +from agentscope.server import RpcAgentServerLauncher from agentscope.message import Msg from agentscope.message import PlaceholderMessage from agentscope.message import deserialize @@ -220,7 +220,7 @@ def test_single_rpc_agent_server(self) -> None: def test_connect_to_an_existing_rpc_server(self) -> None: """test connecting to an existing server""" - launcher = AgentServerLauncher( + launcher = RpcAgentServerLauncher( # choose port automatically host="127.0.0.1", port=12010, @@ -420,7 +420,7 @@ def test_standalone_multiprocess_init(self) -> None: def test_multi_agent_in_same_server(self) -> None: """test agent server with multi agent""" - launcher = AgentServerLauncher( + launcher = RpcAgentServerLauncher( host="127.0.0.1", port=12010, local_mode=False, @@ -548,14 +548,14 @@ def test_error_handling(self) -> None: def test_agent_nesting(self) -> None: """Test agent nesting""" host = "localhost" - launcher1 = AgentServerLauncher( + launcher1 = RpcAgentServerLauncher( # choose port automatically host=host, port=12010, local_mode=False, custom_agents=[DemoGatherAgent, DemoGeneratorAgent], ) - launcher2 = AgentServerLauncher( + launcher2 = RpcAgentServerLauncher( # choose port automatically host=host, port=12011, From 49c5dcf0784379303b0189fb2133566de07d6229 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 10 May 2024 18:25:40 +0800 Subject: [PATCH 09/80] add model_config_path and update tutorial --- .../en/source/tutorial/208-distribute.md | 15 ++++--- .../zh_CN/source/tutorial/208-distribute.md | 16 ++++--- src/agentscope/server/launcher.py | 43 +++++++++++++------ 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index e5a16d977..3b9f7a605 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -59,14 +59,16 @@ b = AgentB( #### Independent Process Mode In the Independent Process Mode, we need to start the agent server process on the target machine first. +When starting the agent server process, you need to specify a model config file, which contains the models which can be used in the agent server, the IP address and port of the agent server process For example, start two agent server processes on the two different machines with IP `ip_a` and `ip_b`(called `Machine1` and `Machine2` accrodingly). -You can run the following code on `Machine1`: +You can run the following code on `Machine1`, and make sure you have put your model config file in `model_config_path_a`. ```python # import some packages +# register models which can be used in the server agentscope.init( - ... + model_configs=model_config_path_a, ) # Create an agent service process server = RpcAgentServerLauncher( @@ -82,16 +84,17 @@ server.wait_until_terminate() > For similarity, you can run the following command in your terminal rather than the above code: > > ```shell -> as_server --host ip_a --port 12001 +> as_server --host ip_a --port 12001 --model-config-path model_config_path_a > ``` -And run the following code on `Machine2`: +And put your model config file accordingly in `model_config_path_b` and run the following code on `Machine2`. ```python # import some packages +# register models which can be used in the server agentscope.init( - ... + model_configs=model_config_path_b, ) # Create an agent service process server = RpcAgentServerLauncher( @@ -107,7 +110,7 @@ server.wait_until_terminate() > Similarly, you can run the following command in your terminal to setup the agent server: > > ```shell -> as_server --host ip_b --port 12002 +> as_server --host ip_b --port 12002 --model-config-path model_config_path_b > ``` Then, you can connect to the agent servers from the main process with the following code. diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index ab4f24520..0a576d228 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -57,15 +57,16 @@ b = AgentB( #### 独立进程模式 -在独立进程模式中,需要首先在目标机器上启动智能体服务器进程。 +在独立进程模式中,需要首先在目标机器上启动智能体服务器进程,启动时需要提供该服务器能够使用的模型的配置信息,以及服务器的 IP 和端口号。 例如想要将两个智能体服务进程部署在 IP 分别为 `ip_a` 和 `ip_b` 的机器上(假设这两台机器分别为`Machine1` 和 `Machine2`)。 -你可以先在 `Machine1` 上运行如下代码: +你可以先在 `Machine1` 上运行如下代码,运行之前请确保已经将模型配置文件放置在 `model_config_path_a` 位置。: ```python # import some packages +# register models which can be used in the server agentscope.init( - ... + model_configs=model_config_path_a, ) # Create an agent service process server = RpcAgentServerLauncher( @@ -81,16 +82,17 @@ server.wait_until_terminate() > 为了进一步简化使用,可以在命令行中输入如下指令来代替上述代码: > > ```shell -> as_server --host ip_a --port 12001 +> as_server --host ip_a --port 12001 --model-config-path model_config_path_a > ``` -之后在 `Machine2` 上运行如下代码: +在 `Machine2` 上运行如下代码,这里同样要确保已经将模型配置文件放置在 `model_config_path_b` 位置。 ```python # import some packages +# register models which can be used in the server agentscope.init( - ... + model_configs=model_config_path_b, ) # Create an agent service process server = RpcAgentServerLauncher( @@ -106,7 +108,7 @@ server.wait_until_terminate() > 这里也同样可以用如下指令来代替上面的代码。 > > ```shell -> as_server --host ip_b --port 12002 +> as_server --host ip_b --port 12002 --model-config-path model_config_path_b > ``` 接下来,就可以使用如下代码从主进程中连接这两个智能体服务器进程。 diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index 34c09f1e7..abd13a24d 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -16,12 +16,13 @@ except ImportError: grpc = None -from .servicer import AgentServerServicer -from ..agents.agent import AgentBase -from ..utils.tools import _get_timestamp +import agentscope +from agentscope.server.servicer import AgentServerServicer +from agentscope.agents.agent import AgentBase +from agentscope.utils.tools import _get_timestamp try: - from ..rpc.rpc_agent_pb2_grpc import ( + from agentscope.rpc.rpc_agent_pb2_grpc import ( add_RpcAgentServicer_to_server, ) except ModuleNotFoundError: @@ -390,20 +391,21 @@ def as_server() -> None: * `--host`: the hostname of the server. * `--port`: the socket port of the server. - * `--max_pool_size`: max number of task results that the server can + * `--max-pool-size`: max number of task results that the server can accommodate. - * `--max_timeout_seconds`: max timeout seconds of a task. - * `--local_mode`: whether the started agent server only listens to + * `--max-timeout-seconds`: max timeout seconds of a task. + * `--local-mode`: whether the started agent server only listens to local requests. + * `--model-config-path`: the path to the model config json file - In most cases, you only need to specify the `--host` and `--port`. + In most cases, you only need to specify the `--host`, `--port` and + `--model-config-path`. .. code-block:: shell - as_server --host localhost --port 12345 - - """ + as_server --host localhost --port 12345 --model-config-path config.json + """ # noqa parser = argparse.ArgumentParser() parser.add_argument( "--host", @@ -418,24 +420,37 @@ def as_server() -> None: help="socket port of the server", ) parser.add_argument( - "--max_pool_size", + "--max-pool-size", type=int, default=8192, help="max number of task results that the server can accommodate", ) parser.add_argument( - "--max_timeout_seconds", + "--max-timeout-seconds", type=int, default=1800, help="max timeout for task results", ) parser.add_argument( - "--local_mode", + "--local-mode", type=bool, default=False, help="whether the started agent server only listens to local requests", ) + parser.add_argument( + "--model-config-path", + type=str, + help="path to the model config json file", + ) args = parser.parse_args() + agentscope.init( + project="agent_server", + name=f"server_{args.host}:{args.port}", + runtime_id=_get_timestamp( + "server_{}_{}_%y%m%d-%H%M%S", + ).format(args.host, args.port), + model_configs=args.model_config_path, + ) launcher = RpcAgentServerLauncher( host=args.host, port=args.port, From b635e35def0409c43befe5b361d787415b14f338 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Mon, 13 May 2024 10:32:02 +0800 Subject: [PATCH 10/80] update tutorial --- docs/sphinx_doc/en/source/tutorial/208-distribute.md | 2 +- docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 3b9f7a605..6aa7c205c 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -61,7 +61,7 @@ b = AgentB( In the Independent Process Mode, we need to start the agent server process on the target machine first. When starting the agent server process, you need to specify a model config file, which contains the models which can be used in the agent server, the IP address and port of the agent server process For example, start two agent server processes on the two different machines with IP `ip_a` and `ip_b`(called `Machine1` and `Machine2` accrodingly). -You can run the following code on `Machine1`, and make sure you have put your model config file in `model_config_path_a`. +You can run the following code on `Machine1`, and make sure you have put your model config file in `model_config_path_a`. The example model config file instances are located under `examples/model_configs_template`. ```python # import some packages diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index 0a576d228..ecbe50a6a 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -59,7 +59,7 @@ b = AgentB( 在独立进程模式中,需要首先在目标机器上启动智能体服务器进程,启动时需要提供该服务器能够使用的模型的配置信息,以及服务器的 IP 和端口号。 例如想要将两个智能体服务进程部署在 IP 分别为 `ip_a` 和 `ip_b` 的机器上(假设这两台机器分别为`Machine1` 和 `Machine2`)。 -你可以先在 `Machine1` 上运行如下代码,运行之前请确保已经将模型配置文件放置在 `model_config_path_a` 位置。: +你可以先在 `Machine1` 上运行如下代码,运行之前请确保已经将模型配置文件放置在 `model_config_path_a` 位置,模型配置文件样例可参考 `examples/model_configs_template`。 ```python # import some packages From c96156d788385217fe3b1004cd60a70fad431f75 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 15 May 2024 10:18:13 +0800 Subject: [PATCH 11/80] fix pre-commit --- src/agentscope/server/servicer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py index e6b4749cd..0b3dd179b 100644 --- a/src/agentscope/server/servicer.py +++ b/src/agentscope/server/servicer.py @@ -21,7 +21,10 @@ grpc = ImportErrorReporter(import_error, "distribute") ServicerContext = ImportErrorReporter(import_error, "distribute") ExpiringDict = ImportErrorReporter(import_error, "distribute") - RpcMsg = ImportErrorReporter(import_error, "distribute") + RpcMsg = ImportErrorReporter( # type: ignore[misc] + import_error, + "distribute", + ) RpcAgentServicer = ImportErrorReporter(import_error, "distribute") from ..agents.agent import AgentBase From bf86cc5995ae94c90098af840c7e8b04036a8de2 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Thu, 16 May 2024 20:56:00 +0800 Subject: [PATCH 12/80] fix comments --- .../en/source/tutorial/208-distribute.md | 4 +- .../zh_CN/source/tutorial/208-distribute.md | 4 +- src/agentscope/server/launcher.py | 54 ++++--------------- src/agentscope/server/servicer.py | 2 +- src/agentscope/utils/tools.py | 39 +++++++++++++- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/docs/sphinx_doc/en/source/tutorial/208-distribute.md b/docs/sphinx_doc/en/source/tutorial/208-distribute.md index 6aa7c205c..0381a13f1 100644 --- a/docs/sphinx_doc/en/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/en/source/tutorial/208-distribute.md @@ -61,7 +61,7 @@ b = AgentB( In the Independent Process Mode, we need to start the agent server process on the target machine first. When starting the agent server process, you need to specify a model config file, which contains the models which can be used in the agent server, the IP address and port of the agent server process For example, start two agent server processes on the two different machines with IP `ip_a` and `ip_b`(called `Machine1` and `Machine2` accrodingly). -You can run the following code on `Machine1`, and make sure you have put your model config file in `model_config_path_a`. The example model config file instances are located under `examples/model_configs_template`. +You can run the following code on `Machine1`.Before running, make sure that the machine has access to all models that used in your application, specifically, you need to put your model config file in `model_config_path_a` and set environment variables such as your model API key correctly in `Machine1`. The example model config file instances are located under `examples/model_configs_template`. ```python # import some packages @@ -87,7 +87,7 @@ server.wait_until_terminate() > as_server --host ip_a --port 12001 --model-config-path model_config_path_a > ``` -And put your model config file accordingly in `model_config_path_b` and run the following code on `Machine2`. +Then put your model config file accordingly in `model_config_path_b`, set environment variables, and run the following code on `Machine2`. ```python # import some packages diff --git a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md index ecbe50a6a..a185bd5da 100644 --- a/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md +++ b/docs/sphinx_doc/zh_CN/source/tutorial/208-distribute.md @@ -59,7 +59,7 @@ b = AgentB( 在独立进程模式中,需要首先在目标机器上启动智能体服务器进程,启动时需要提供该服务器能够使用的模型的配置信息,以及服务器的 IP 和端口号。 例如想要将两个智能体服务进程部署在 IP 分别为 `ip_a` 和 `ip_b` 的机器上(假设这两台机器分别为`Machine1` 和 `Machine2`)。 -你可以先在 `Machine1` 上运行如下代码,运行之前请确保已经将模型配置文件放置在 `model_config_path_a` 位置,模型配置文件样例可参考 `examples/model_configs_template`。 +你可以在 `Machine1` 上运行如下代码。在运行之前请确保该机器能够正确访问到应用中所使用的所有模型。具体来讲,需要将用到的所有模型的配置信息放置在 `model_config_path_a` 文件中,并检查API key 等环境变量是否正确设置,模型配置文件样例可参考 `examples/model_configs_template`。 ```python # import some packages @@ -85,7 +85,7 @@ server.wait_until_terminate() > as_server --host ip_a --port 12001 --model-config-path model_config_path_a > ``` -在 `Machine2` 上运行如下代码,这里同样要确保已经将模型配置文件放置在 `model_config_path_b` 位置。 +在 `Machine2` 上运行如下代码,这里同样要确保已经将模型配置文件放置在 `model_config_path_b` 位置并设置环境变量,从而确保运行在该机器上的 Agent 能够正常访问到模型。 ```python # import some packages diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index c5c17ed30..fda6ffa19 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -3,11 +3,10 @@ import os from multiprocessing import Process, Event, Pipe from multiprocessing.synchronize import Event as EventClass -import socket import asyncio import signal import argparse -from typing import Type, Optional +from typing import Type from concurrent import futures from loguru import logger @@ -28,10 +27,13 @@ import agentscope from agentscope.server.servicer import AgentServerServicer from agentscope.agents.agent import AgentBase -from agentscope.utils.tools import _get_timestamp +from agentscope.utils.tools import ( + _get_timestamp, + check_port, +) -def setup_agent_server( +def _setup_agent_server( host: str, port: int, server_id: str, @@ -73,7 +75,7 @@ def setup_agent_server( A list of custom agent classes that are not in `agentscope.agents`. """ asyncio.run( - setup_agent_server_async( + _setup_agent_server_async( host=host, port=port, server_id=server_id, @@ -89,7 +91,7 @@ def setup_agent_server( ) -async def setup_agent_server_async( +async def _setup_agent_server_async( host: str, port: int, server_id: str, @@ -198,42 +200,6 @@ async def shutdown_signal_handler() -> None: ) -def find_available_port() -> int: - """Get an unoccupied socket port number.""" - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.bind(("", 0)) - return s.getsockname()[1] - - -def check_port(port: Optional[int] = None) -> int: - """Check if the port is available. - - Args: - port (`int`): - the port number being checked. - - Returns: - `int`: the port number that passed the check. If the port is found - to be occupied, an available port number will be automatically - returned. - """ - if port is None: - new_port = find_available_port() - logger.warning( - "agent server port is not provided, automatically select " - f"[{new_port}] as the port number.", - ) - return new_port - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - if s.connect_ex(("localhost", port)) == 0: - new_port = find_available_port() - logger.warning( - f"Port [{port}] is occupied, use [{new_port}] instead", - ) - return new_port - return port - - class RpcAgentServerLauncher: """The launcher of AgentServer.""" @@ -309,7 +275,7 @@ def _launch_in_main(self) -> None: f"Launching agent server at [{self.host}:{self.port}]...", ) asyncio.run( - setup_agent_server_async( + _setup_agent_server_async( host=self.host, port=self.port, server_id=self.server_id, @@ -328,7 +294,7 @@ def _launch_in_sub(self) -> None: self.parent_con, child_con = Pipe() start_event = Event() server_process = Process( - target=setup_agent_server, + target=_setup_agent_server, kwargs={ "host": self.host, "port": self.port, diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py index 0b3dd179b..8f6134f81 100644 --- a/src/agentscope/server/servicer.py +++ b/src/agentscope/server/servicer.py @@ -36,7 +36,7 @@ class AgentServerServicer(RpcAgentServicer): - """A Servicer for agent to run on (formerly RpcServerSideWrapper)""" + """A Servicer for RPC Agent Server (formerly RpcServerSideWrapper)""" def __init__( self, diff --git a/src/agentscope/utils/tools.py b/src/agentscope/utils/tools.py index 8ebd23777..8888d99e6 100644 --- a/src/agentscope/utils/tools.py +++ b/src/agentscope/utils/tools.py @@ -6,7 +6,8 @@ import os.path import secrets import string -from typing import Any, Literal, List +import socket +from typing import Any, Literal, List, Optional from urllib.parse import urlparse @@ -61,6 +62,42 @@ def to_dialog_str(item: dict) -> str: return f"{speaker}: {content}" +def find_available_port() -> int: + """Get an unoccupied socket port number.""" + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.bind(("", 0)) + return s.getsockname()[1] + + +def check_port(port: Optional[int] = None) -> int: + """Check if the port is available. + + Args: + port (`int`): + the port number being checked. + + Returns: + `int`: the port number that passed the check. If the port is found + to be occupied, an available port number will be automatically + returned. + """ + if port is None: + new_port = find_available_port() + logger.warning( + "agent server port is not provided, automatically select " + f"[{new_port}] as the port number.", + ) + return new_port + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + if s.connect_ex(("localhost", port)) == 0: + new_port = find_available_port() + logger.warning( + f"Port [{port}] is occupied, use [{new_port}] instead", + ) + return new_port + return port + + def _guess_type_by_extension( url: str, ) -> Literal["image", "audio", "video", "file"]: From c9eaf2df3a167ee9980d95bf97f290d02e8a2951 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 17 May 2024 11:44:27 +0800 Subject: [PATCH 13/80] fix comments --- src/agentscope/agents/agent.py | 16 +++++++--- src/agentscope/server/launcher.py | 49 +++++++++++++++++++++---------- src/agentscope/server/servicer.py | 29 +++++++++++------- 3 files changed, 64 insertions(+), 30 deletions(-) diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 91f45527a..4341952d9 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -384,14 +384,22 @@ def to_dist( port (`int`, defaults to `None`): Port of the rpc agent server. max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. + Only takes effect when `host` and `port` are not filled in. + The max number of agent reply messages that the started agent + server can accommodate. Note that the oldest message will be + deleted after exceeding the pool size. max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. + Only takes effect when `host` and `port` are not filled in. + Maximum time for reply messages to be cached in the launched + agent server. Note that expired messages will be deleted. local_mode (`bool`, defaults to `True`): - Whether the started rpc server only listens to local + Only takes effect when `host` and `port` are not filled in. + Whether the started agent server only listens to local requests. lazy_launch (`bool`, defaults to `True`): - Only launch the server when the agent is called. + Only takes effect when `host` and `port` are not filled in. + If `True`, launch the agent server when the agent is called, + otherwise, launch the agent server immediately. launch_server(`bool`, defaults to `None`): This field has been deprecated and will be removed in future releases. diff --git a/src/agentscope/server/launcher.py b/src/agentscope/server/launcher.py index fda6ffa19..ed5ed7f67 100644 --- a/src/agentscope/server/launcher.py +++ b/src/agentscope/server/launcher.py @@ -68,9 +68,9 @@ def _setup_agent_server( local_mode (`bool`, defaults to `None`): Only listen to local requests. max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. + Max number of agent replies that the server can accommodate. max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. + Timeout for agent replies. custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in `agentscope.agents`. """ @@ -124,11 +124,15 @@ async def _setup_agent_server_async( pipe (`int`, defaults to `None`): A pipe instance used to pass the actual port of the server. local_mode (`bool`, defaults to `None`): - Only listen to local requests. + If `True`, only listen to requests from "localhost", otherwise, + listen to requests from all hosts. max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. + The max number of agent reply messages that the server can + accommodate. Note that the oldest message will be deleted + after exceeding the pool size. max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. + Maximum time for reply messages to be cached in the server. + Note that expired messages will be deleted. custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in `agentscope.agents`. """ @@ -224,12 +228,15 @@ def __init__( port (`int`, defaults to `None`): Socket port of the agent server. max_pool_size (`int`, defaults to `8192`): - Max number of task results that the server can accommodate. + The max number of agent reply messages that the server can + accommodate. Note that the oldest message will be deleted + after exceeding the pool size. max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. + Maximum time for reply messages to be cached in the server. + Note that expired messages will be deleted. local_mode (`bool`, defaults to `False`): - Whether the started server only listens to local - requests. + If `True`, only listen to requests from "localhost", otherwise, + listen to requests from all hosts. custom_agents (`list`, defaults to `None`): A list of custom agent classes that are not in `agentscope.agents`. @@ -359,9 +366,11 @@ def as_server() -> None: * `--host`: the hostname of the server. * `--port`: the socket port of the server. - * `--max-pool-size`: max number of task results that the server can - accommodate. - * `--max-timeout-seconds`: max timeout seconds of a task. + * `--max-pool-size`: max number of agent reply messages that the server + can accommodate. Note that the oldest message will be deleted + after exceeding the pool size. + * `--max-timeout-seconds`: max time for reply messages to be cached + in the server. Note that expired messages will be deleted. * `--local-mode`: whether the started agent server only listens to local requests. * `--model-config-path`: the path to the model config json file @@ -391,19 +400,29 @@ def as_server() -> None: "--max-pool-size", type=int, default=8192, - help="max number of task results that the server can accommodate", + help=( + "max number of agent reply messages that the server " + "can accommodate. Note that the oldest message will be deleted " + "after exceeding the pool size." + ), ) parser.add_argument( "--max-timeout-seconds", type=int, default=1800, - help="max timeout for task results", + help=( + "max time for agent reply messages to be cached" + "in the server. Note that expired messages will be deleted." + ), ) parser.add_argument( "--local-mode", type=bool, default=False, - help="whether the started agent server only listens to local requests", + help=( + "If `True`, only listen to requests from 'localhost', otherwise, " + "listen to requests from all hosts." + ), ) parser.add_argument( "--model-config-path", diff --git a/src/agentscope/server/servicer.py b/src/agentscope/server/servicer.py index 8f6134f81..53c63425f 100644 --- a/src/agentscope/server/servicer.py +++ b/src/agentscope/server/servicer.py @@ -53,12 +53,12 @@ def __init__( port (`int`, defaults to `None`): Port of the rpc agent server. max_pool_size (`int`, defaults to `8192`): - The max number of task results that the server can - accommodate. Note that the oldest result will be deleted + The max number of agent reply messages that the server can + accommodate. Note that the oldest message will be deleted after exceeding the pool size. max_timeout_seconds (`int`, defaults to `1800`): - Timeout for task results. Note that expired results will be - deleted. + Maximum time for reply messages to be cached in the server. + Note that expired messages will be deleted. """ self.host = host self.port = port @@ -73,7 +73,8 @@ def __init__( self.agent_pool: dict[str, AgentBase] = {} def get_task_id(self) -> int: - """Get the auto-increment task id.""" + """Get the auto-increment task id. + Each reply call will get a unique task id.""" with self.task_id_lock: self.task_id_counter += 1 return self.task_id_counter @@ -191,11 +192,11 @@ def _reply(self, request: RpcMsg) -> RpcMsg: ) def _get(self, request: RpcMsg) -> RpcMsg: - """Get function of RpcAgentService + """Get a reply message with specific task_id. Args: request (`RpcMsg`): - Identifier of message, with json format:: + The task id that generated this message, with json format:: { 'task_id': int @@ -215,7 +216,7 @@ def _get(self, request: RpcMsg) -> RpcMsg: return RpcMsg(value=result.serialize()) def _observe(self, request: RpcMsg) -> RpcMsg: - """Observe function of RpcAgentService + """Observe function of the original agent. Args: request (`RpcMsg`): @@ -232,7 +233,7 @@ def _observe(self, request: RpcMsg) -> RpcMsg: return RpcMsg() def _create_agent(self, request: RpcMsg) -> RpcMsg: - """Create a new agent instance for the agent_id. + """Create a new agent instance with the given agent_id. Args: request (RpcMsg): request message with a `agent_id` field. @@ -272,7 +273,7 @@ def _clone_agent(self, request: RpcMsg) -> RpcMsg: return RpcMsg(value=new_agent.agent_id) # type: ignore[arg-type] def _delete_agent(self, request: RpcMsg) -> RpcMsg: - """Delete the agent instance of the specific sesssion_id. + """Delete the agent instance of the specific agent_id. Args: request (RpcMsg): request message with a `agent_id` field. @@ -286,7 +287,13 @@ def process_messages( agent_id: str, task_msg: dict = None, ) -> None: - """Task processing.""" + """Processing an input message and generate its reply message. + + Args: + task_id (`int`): task id of the input message, . + agent_id (`str`): the id of the agent that accepted the message. + task_msg (`dict`): the input message. + """ if isinstance(task_msg, PlaceholderMessage): task_msg.update_value() cond = self.result_pool[task_id] From 0c1fbc17ac1c40009e81983ce07060943db3159b Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Fri, 17 May 2024 15:52:48 +0800 Subject: [PATCH 14/80] add websocket for message handling --- src/agentscope/web/_app.py | 176 ++++++++++++++++++++++++++++++++++++- 1 file changed, 173 insertions(+), 3 deletions(-) diff --git a/src/agentscope/web/_app.py b/src/agentscope/web/_app.py index 3918a3ff6..5f516443d 100644 --- a/src/agentscope/web/_app.py +++ b/src/agentscope/web/_app.py @@ -2,12 +2,17 @@ """The main entry point of the web UI.""" import json import os +from datetime import datetime -from flask import Flask, jsonify, render_template, Response +from flask import Flask, request, jsonify, render_template, Response from flask_cors import CORS -from flask_socketio import SocketIO +from flask_sqlalchemy import SQLAlchemy +from flask_socketio import SocketIO, emit, join_room, leave_room + app = Flask(__name__) +app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///agentscope.db" +db = SQLAlchemy(app) socketio = SocketIO(app) CORS(app) # This will enable CORS for all routes @@ -15,6 +20,129 @@ PATH_SAVE = "" +class Run(db.Model): + """Run object.""" + + id = db.Column(db.String, primary_key=True) + project = db.Column(db.String) + name = db.Column(db.String) + script_path = db.Column(db.String) + run_dir = db.Column(db.String) + create_time = db.Column(db.DateTime, default=datetime.now) + + +class Server(db.Model): + """Server object.""" + + server_id = db.Column(db.String, primary_key=True) + server_host = db.Column(db.String) + server_port = db.Column(db.Integer) + + +class Message(db.Model): + """Message object.""" + + id = db.Column(db.Integer, primary_key=True) + run_id = db.Column(db.String, db.ForeignKey("run.id"), nullable=False) + name = db.Column(db.String) + content = db.Column(db.str) + url = db.Column(db.str) + + +@app.route("/api/register/run", methods=["POST"]) +def register_run(): + """ + Registers a run of an agentscope application. + The running process will then be displayed as a page. + """ + # Extract the input data from the request + data = request.json + run_id = data.get("run_id") + project = data.get("project") + name = data.get("name") + run_dir = data.get("run_dir") + script_path = data.get("script_path") + # check if the run_id is already in the database + if Run.query.filter_by(id=run_id).first(): + return jsonify(status="error", msg=f"run_id {run_id} already exists.") + else: + db.session.add( + Run( + id=run_id, + project=project, + name=name, + run_dir=run_dir, + script_path=script_path, + ) + ) + db.session.commit() + return jsonify(status="ok", msg="") + + +@app.route("/api/register/server", methods=["POST"]) +def register_server(): + """ + Registers an agent server. + """ + data = request.json + server_id = data.get("server_id") + host = data.get("host") + port = data.get("port") + run_dir = data.get("run_dir") + + if Server.query.filter_by(server_id=server_id).first(): + return jsonify(status="error", msg="server_id already exists") + else: + db.session.add( + Server( + server_id=server_id, + server_host=host, + server_port=port, + run_dir=run_dir, + ) + ) + return jsonify(status="ok", msg="") + + +@app.route("/api/message/put", methods=["POST"]) +def put_message(): + """ + Used by the application to speak a message to the Hub. + """ + data = request.json + run_id = data["run_id"] + name = data["name"] + content = data["content"] + url = data["url"] + try: + new_message = Message( + run_id=run_id, name=name, content=content, url=url + ) + db.session.add(new_message) + db.session.commit() + except Exception as e: + return jsonify(status="ok", msg=e) + socketio.emit( + "display_message", + { + "run_id": run_id, + "name": name, + "content": content, + "url": url, + }, + room=run_id, + ) + return jsonify(status="ok", msg="") + + +@app.route("/studio/", methods=["GET"]) +def studio_page(run_id): + if Run.query.filter_by(id=run_id).first() is None: + return jsonify(status="error", msg="run_id not exists") + messages = Message.query.filter_by(run_id=run_id).all() + return render_template("chat.html", messages=messages, run_id=run_id) + + @app.route("/getProjects", methods=["GET"]) def get_projects() -> Response: """Get all the projects in the runs directory.""" @@ -88,6 +216,47 @@ def run_detail(run_dir: str) -> str: return render_template("run.html", runInfo=logging_and_dialog) +@socketio.on("user_input") +def user_input(data): + run_id = data["run_id"] + name = data["name"] + content = data["content"] + url = data.get("url", None) + new_message = Message(run_id=run_id, name=name, content=content, url=url) + db.session.add(new_message) + db.session.commit() + emit( + "display_message", + { + "run_id": run_id, + "name": name, + "content": content, + "url": url, + }, + room=run_id, + ) + emit( + "fetch_user_input", + { + "run_id": run_id, + "name": name, + "content": content, + "url": url, + }, + room=run_id, + ) + + +@socketio.on("join") +def on_join(data): + join_room(data["run_id"]) + + +@socketio.on("leave") +def on_leave(data): + leave_room(data["run_id"]) + + @socketio.on("connect") def on_connect() -> None: """Execute when a client is connected.""" @@ -111,7 +280,8 @@ def init( if not os.path.exists(path_save): raise FileNotFoundError(f"The path {path_save} does not exist.") - + with app.app_context(): + db.create_all() PATH_SAVE = path_save socketio.run( app, From 7215974f49761c63ecd36e857bc646a9f3f3ef3e Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Mon, 20 May 2024 17:20:08 +0800 Subject: [PATCH 15/80] init agentscope studio plus --- src/agentscope/_init.py | 23 +++++ src/agentscope/_runtime.py | 4 + src/agentscope/agents/agent.py | 14 +++ src/agentscope/agents/user_agent.py | 56 +++++----- src/agentscope/web/_app.py | 111 ++++++++++---------- src/agentscope/web/client.py | 135 +++++++++++++++++++++++++ src/agentscope/web/templates/chat.html | 39 +++++++ 7 files changed, 302 insertions(+), 80 deletions(-) create mode 100644 src/agentscope/web/client.py create mode 100644 src/agentscope/web/templates/chat.html diff --git a/src/agentscope/_init.py b/src/agentscope/_init.py index dff68e585..3184ba176 100644 --- a/src/agentscope/_init.py +++ b/src/agentscope/_init.py @@ -13,6 +13,7 @@ from .models import read_model_configs from .constants import _DEFAULT_DIR from .constants import _DEFAULT_LOG_LEVEL +from .web.client import HttpClient # init setting _INIT_SETTINGS = {} @@ -30,6 +31,7 @@ def init( logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL, runtime_id: Optional[str] = None, agent_configs: Optional[Union[str, list, dict]] = None, + studio_url: Optional[str] = None, ) -> Sequence[AgentBase]: """A unified entry to initialize the package, including model configs, runtime names, saving directories and logging settings. @@ -65,6 +67,8 @@ def init( which can be loaded by json.loads(). One agent config should cover the required arguments to initialize a specific agent object, otherwise the default values will be used. + studio_url (`Optional[str]`, defaults to `None`): + The url of the studio. """ init_process( model_configs=model_configs, @@ -76,6 +80,7 @@ def init( save_log=save_log, use_monitor=use_monitor, logger_level=logger_level, + studio_url=studio_url, ) # save init settings for subprocess @@ -128,6 +133,7 @@ def init_process( save_log: bool = False, use_monitor: bool = True, logger_level: LOG_LEVEL = _DEFAULT_LOG_LEVEL, + studio_url: Optional[str] = None, ) -> None: """An entry to initialize the package in a process. @@ -157,10 +163,16 @@ def init_process( # Init the runtime if project is not None: _runtime.project = project + else: + project = _runtime.project if name is not None: _runtime.name = name + else: + name = _runtime.name if runtime_id is not None: _runtime.runtime_id = runtime_id + else: + runtime_id = _runtime.runtime_id # Init logger dir_log = str(file_manager.dir_log) if save_log else None @@ -178,3 +190,14 @@ def init_process( db_path=file_manager.path_db, impl_type="sqlite" if use_monitor else "dummy", ) + if studio_url is not None: + client = HttpClient( + studio_url=studio_url, + run_id=_runtime.runtime_id, + ) + client.register_run( + project=project, + name=name, + run_dir=file_manager.dir, + ) + _runtime.studio_client = client diff --git a/src/agentscope/_runtime.py b/src/agentscope/_runtime.py index 178138873..09e65b906 100644 --- a/src/agentscope/_runtime.py +++ b/src/agentscope/_runtime.py @@ -5,6 +5,7 @@ from agentscope.utils.tools import _get_timestamp from agentscope.utils.tools import _generate_random_code +from agentscope.web.client import HttpClient _RUNTIME_ID_FORMAT = "run_%Y%m%d-%H%M%S_{}" _RUNTIME_TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S" @@ -24,6 +25,9 @@ class _Runtime: """The id for runtime, which is used to identify the this runtime and name the saving directory.""" + studio_client: HttpClient = None + """The client of AgentScope Studio.""" + _timestamp: datetime = datetime.now() """The timestamp of when the runtime is initialized.""" diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 4341952d9..080632da0 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -14,6 +14,7 @@ from agentscope.agents.operator import Operator from agentscope.models import load_model_by_config_name from agentscope.memory import TemporaryMemory +from agentscope._runtime import _runtime class _AgentMeta(ABCMeta): @@ -305,6 +306,19 @@ def speak( ) -> None: """Speak out the content generated by the agent.""" logger.chat(content) + if _runtime.studio_client is not None: + if isinstance(content, dict): + _runtime.studio_client.send_message( + name=content.get("name", ""), + content=content.get("content", ""), + url=content.get("url", ""), + ) + else: + _runtime.studio_client.send_message( + name="", + content=str(content), + url="", + ) def observe(self, x: Union[dict, Sequence[dict]]) -> None: """Observe the input, store it in memory without response to it. diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index ee97a935f..fef577df9 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -6,6 +6,7 @@ from loguru import logger from agentscope.agents import AgentBase +from agentscope._runtime import _runtime from agentscope.message import Msg from agentscope.web.studio.utils import user_input @@ -29,6 +30,14 @@ def __init__(self, name: str = "User", require_url: bool = False) -> None: self.name = name self.require_url = require_url + if _runtime.studio_client is not None: + self.input_client = ( + _runtime.studio_client.generate_user_input_client( + self.agent_id, + ) + ) + else: + self.input_client = None def reply( self, @@ -67,23 +76,29 @@ def reply( # TODO: To avoid order confusion, because `input` print much quicker # than logger.chat - time.sleep(0.5) - content = user_input(timeout=timeout) - - kwargs = {} - if required_keys is not None: - if isinstance(required_keys, str): - required_keys = [required_keys] - - for key in required_keys: - kwargs[key] = input(f"{key}: ") - - # Input url of file, image, video, audio or website - url = None - if self.require_url: - url = input("URL (or Enter to skip): ") - if url == "": - url = None + if self.input_client: + logger.info( + f"Waiting for input from {self.input_client.studio_url}...", + ) + raw_input = self.input_client.get_user_input() + content = raw_input["content"] + else: + time.sleep(0.5) + content = user_input(timeout=timeout) + kwargs = {} + if required_keys is not None: + if isinstance(required_keys, str): + required_keys = [required_keys] + + for key in required_keys: + kwargs[key] = input(f"{key}: ") + + # Input url of file, image, video, audio or website + url = None + if self.require_url: + url = input("URL (or Enter to skip): ") + if url == "": + url = None # Add additional keys msg = Msg( @@ -101,10 +116,3 @@ def reply( self.memory.add(msg) return msg - - def speak( - self, - content: Union[str, dict], - ) -> None: - """Speak the content to the audience.""" - logger.chat(content, disable_studio=True) diff --git a/src/agentscope/web/_app.py b/src/agentscope/web/_app.py index 5f516443d..6e8d44fee 100644 --- a/src/agentscope/web/_app.py +++ b/src/agentscope/web/_app.py @@ -4,10 +4,10 @@ import os from datetime import datetime -from flask import Flask, request, jsonify, render_template, Response +from flask import Flask, request, jsonify, render_template, Response, abort from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy -from flask_socketio import SocketIO, emit, join_room, leave_room +from flask_socketio import SocketIO, join_room, leave_room app = Flask(__name__) @@ -20,7 +20,7 @@ PATH_SAVE = "" -class Run(db.Model): +class Run(db.Model): # type: ignore[name-defined] """Run object.""" id = db.Column(db.String, primary_key=True) @@ -31,7 +31,7 @@ class Run(db.Model): create_time = db.Column(db.DateTime, default=datetime.now) -class Server(db.Model): +class Server(db.Model): # type: ignore[name-defined] """Server object.""" server_id = db.Column(db.String, primary_key=True) @@ -39,18 +39,23 @@ class Server(db.Model): server_port = db.Column(db.Integer) -class Message(db.Model): +class Message(db.Model): # type: ignore[name-defined] """Message object.""" id = db.Column(db.Integer, primary_key=True) run_id = db.Column(db.String, db.ForeignKey("run.id"), nullable=False) name = db.Column(db.String) - content = db.Column(db.str) - url = db.Column(db.str) + content = db.Column(db.String) + url = db.Column(db.String) + + +def get_history_messages(run_id: str) -> list: + """Interface to get history messages. (Query from database for now)""" + return Message.query.filter_by(run_id=run_id).all() @app.route("/api/register/run", methods=["POST"]) -def register_run(): +def register_run() -> Response: """ Registers a run of an agentscope application. The running process will then be displayed as a page. @@ -61,26 +66,25 @@ def register_run(): project = data.get("project") name = data.get("name") run_dir = data.get("run_dir") - script_path = data.get("script_path") # check if the run_id is already in the database if Run.query.filter_by(id=run_id).first(): - return jsonify(status="error", msg=f"run_id {run_id} already exists.") - else: - db.session.add( - Run( - id=run_id, - project=project, - name=name, - run_dir=run_dir, - script_path=script_path, - ) - ) - db.session.commit() - return jsonify(status="ok", msg="") + print(f"Run id {run_id} already exists.") + abort(400, f"RUN_ID {run_id} already exists") + db.session.add( + Run( + id=run_id, + project=project, + name=name, + run_dir=run_dir, + ), + ) + db.session.commit() + print(f"Register Run id {run_id}.") + return jsonify(status="ok", msg="") @app.route("/api/register/server", methods=["POST"]) -def register_server(): +def register_server() -> Response: """ Registers an agent server. """ @@ -99,13 +103,13 @@ def register_server(): server_host=host, server_port=port, run_dir=run_dir, - ) + ), ) return jsonify(status="ok", msg="") @app.route("/api/message/put", methods=["POST"]) -def put_message(): +def put_message() -> Response: """ Used by the application to speak a message to the Hub. """ @@ -113,15 +117,19 @@ def put_message(): run_id = data["run_id"] name = data["name"] content = data["content"] - url = data["url"] + url = data.get("url", None) try: new_message = Message( - run_id=run_id, name=name, content=content, url=url + run_id=run_id, + name=name, + content=content, + url=url, ) db.session.add(new_message) db.session.commit() except Exception as e: - return jsonify(status="ok", msg=e) + print(e) + abort(400, "Fail to put message") socketio.emit( "display_message", { @@ -136,7 +144,8 @@ def put_message(): @app.route("/studio/", methods=["GET"]) -def studio_page(run_id): +def studio_page(run_id: str) -> str: + """Studio page.""" if Run.query.filter_by(id=run_id).first() is None: return jsonify(status="error", msg="run_id not exists") messages = Message.query.filter_by(run_id=run_id).all() @@ -217,29 +226,15 @@ def run_detail(run_dir: str) -> str: @socketio.on("user_input") -def user_input(data): +def user_input(data: dict) -> None: + """Get user input and send to the agent""" run_id = data["run_id"] - name = data["name"] content = data["content"] url = data.get("url", None) - new_message = Message(run_id=run_id, name=name, content=content, url=url) - db.session.add(new_message) - db.session.commit() - emit( - "display_message", - { - "run_id": run_id, - "name": name, - "content": content, - "url": url, - }, - room=run_id, - ) - emit( + socketio.emit( "fetch_user_input", { "run_id": run_id, - "name": name, "content": content, "url": url, }, @@ -247,16 +242,6 @@ def user_input(data): ) -@socketio.on("join") -def on_join(data): - join_room(data["run_id"]) - - -@socketio.on("leave") -def on_leave(data): - leave_room(data["run_id"]) - - @socketio.on("connect") def on_connect() -> None: """Execute when a client is connected.""" @@ -269,6 +254,20 @@ def on_disconnect() -> None: print("Client disconnected") +@socketio.on("join") +def on_join(data: dict) -> None: + """Join a websocket room""" + run_id = data["run_id"] + join_room(run_id) + + +@socketio.on("leave") +def on_leave(data: dict) -> None: + """Leave a websocket room""" + run_id = data["run_id"] + leave_room(run_id) + + def init( path_save: str, host: str = "127.0.0.1", diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py new file mode 100644 index 000000000..6ecf1a92f --- /dev/null +++ b/src/agentscope/web/client.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- +"""The client for agentscope platform.""" +from threading import Event +from typing import Optional +import requests + +import socketio +from loguru import logger + + +class WebSocketClient: + """WebSocket Client of AgentScope Studio, only used to obtain + input messages from users.""" + + def __init__( + self, + studio_url: str, + run_id: str, + agent_id: str, + ) -> None: + self.studio_url = studio_url + self.run_id = run_id + self.agent_id = agent_id + self.user_input = None + self.sio = socketio.Client() + self.input_event = Event() + + @self.sio.event + def connect() -> None: + logger.info("Connected to Studio") + self.sio.emit("join", {"run_id": self.run_id}) + + @self.sio.event + def disconnect() -> None: + logger.info("Disconnected from Studio") + self.sio.emit("leave", {"run_id": self.run_id}) + + @self.sio.on("fetch_user_input") + def on_fetch_user_input(data: dict) -> None: + self.user_input = data + self.input_event.set() + + self.sio.connect(f"{self.studio_url}") + + def get_user_input(self) -> Optional[dict]: + """Get user input from studio in real-time. + + Note: + Only agents that requires user inputs should call this function. + Calling this function will block the calling thread until the user + input is received. + """ + self.input_event.clear() + self.sio.emit("request_user_input") + self.input_event.wait() + return self.user_input + + def close(self) -> None: + """Close the websocket connection.""" + self.sio.disconnect() + + +class HttpClient: + """HTTP client for the AgentScope Studio, used to handle interactions with + the Studio except for the user input (which need websocket connections).""" + + def __init__( + self, + studio_url: str, + run_id: str, + ) -> None: + self.studio_url = studio_url + self.run_id = run_id + + def generate_user_input_client(self, agent_id: str) -> WebSocketClient: + """Generate a websocket client for a specifc user agent.""" + return WebSocketClient(self.studio_url, self.run_id, agent_id=agent_id) + + def register_run( + self, + project: str, + name: str, + run_dir: str, + ) -> bool: + """Register a run to the AgentScope Studio. + + Args: + run_id (str): _description_ + project (str): _description_ + name (str): _description_ + run_dir (str): _description_ + + Returns: + bool: _description_ + """ + url = f"{self.studio_url}/api/register/run" + resp = requests.post( + url, + json={ + "run_id": self.run_id, + "project": project, + "name": name, + "run_dir": run_dir, + }, + timeout=10, # todo: configurable timeout + ) + if resp == 200: + return True + else: + logger.warning(f"Fail to register to studio: {resp}") + raise RuntimeError(f"Fail to register to studio: {resp}") + + def send_message( + self, + name: str, + content: str, + url: str = None, + ) -> bool: + """Send a message to the studio.""" + url = f"{self.studio_url}/api/message/put" + resp = requests.post( + url, + json={ + "run_id": self.run_id, + "name": name, + "content": content, + "url": None, + }, + timeout=10, + ) + if resp.status_code == 200: + return True + else: + logger.warning(f"Fail to send message to studio: {resp}") + return False diff --git a/src/agentscope/web/templates/chat.html b/src/agentscope/web/templates/chat.html new file mode 100644 index 000000000..a504ed503 --- /dev/null +++ b/src/agentscope/web/templates/chat.html @@ -0,0 +1,39 @@ + + + + + Chat + + + + +
    + {% for message in messages %} +
  • {{ message.name }}: {{ message.content }}
  • + {% endfor %} +
+ + + + From e06a0ec90dd530c35818923a1f101f1e2a81460a Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Tue, 21 May 2024 14:33:32 +0800 Subject: [PATCH 16/80] add api for get messages and runs --- src/agentscope/web/_app.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/agentscope/web/_app.py b/src/agentscope/web/_app.py index 6e8d44fee..683d0c438 100644 --- a/src/agentscope/web/_app.py +++ b/src/agentscope/web/_app.py @@ -54,6 +54,11 @@ def get_history_messages(run_id: str) -> list: return Message.query.filter_by(run_id=run_id).all() +def get_runs() -> list: + """Interface to get all runs. (Query from database for now)""" + return Run.all() + + @app.route("/api/register/run", methods=["POST"]) def register_run() -> Response: """ @@ -143,6 +148,18 @@ def put_message() -> Response: return jsonify(status="ok", msg="") +@app.route("/api/messages/", methods=["GET"]) +def get_messages(run_id: str) -> list: + """Get the history messages of specific run_id.""" + return get_history_messages(run_id=run_id) + + +@app.route("/api/runs", methods=["GET"]) +def get_all_runs() -> list: + """Get all runs.""" + return get_runs() + + @app.route("/studio/", methods=["GET"]) def studio_page(run_id: str) -> str: """Studio page.""" From 271c8e4905cbcf908e224fca59b548a2a9d5052b Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Tue, 21 May 2024 14:52:52 +0800 Subject: [PATCH 17/80] update api of get messages and runs --- src/agentscope/web/_app.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/agentscope/web/_app.py b/src/agentscope/web/_app.py index 683d0c438..d5384826b 100644 --- a/src/agentscope/web/_app.py +++ b/src/agentscope/web/_app.py @@ -51,12 +51,31 @@ class Message(db.Model): # type: ignore[name-defined] def get_history_messages(run_id: str) -> list: """Interface to get history messages. (Query from database for now)""" - return Message.query.filter_by(run_id=run_id).all() + messages = Message.query.filter_by(run_id=run_id).all() + return [ + { + "name": message.name, + "content": message.content, + "url": message.url, + } + for message in messages + ] def get_runs() -> list: """Interface to get all runs. (Query from database for now)""" - return Run.all() + runs = Run.query.all() + return [ + { + "id": run.id, + "project": run.project, + "name": run.name, + "script_path": run.script_path, + "run_dir": run.run_dir, + "create_time": run.create_time.isoformat(), + } + for run in runs + ] @app.route("/api/register/run", methods=["POST"]) From b02c7b271f0d846b7655818f55a7be1a46db343e Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Tue, 21 May 2024 20:22:25 +0800 Subject: [PATCH 18/80] add more fields into message --- src/agentscope/agents/agent.py | 10 ++++++++-- src/agentscope/agents/user_agent.py | 2 ++ src/agentscope/web/_app.py | 12 ++++++++++++ src/agentscope/web/client.py | 10 ++++++++-- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 080632da0..9e60538b7 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -310,14 +310,20 @@ def speak( if isinstance(content, dict): _runtime.studio_client.send_message( name=content.get("name", ""), + role=content.get("role", "assistant"), content=content.get("content", ""), - url=content.get("url", ""), + metadata=content.get("metadata", {}), + url=content.get("url", None), + timestamp=content.get("timestamp", None), ) else: _runtime.studio_client.send_message( name="", + role="assistant", content=str(content), - url="", + metadata={}, + url=None, + timestamp=None, ) def observe(self, x: Union[dict, Sequence[dict]]) -> None: diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index fef577df9..171766ef0 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -82,6 +82,8 @@ def reply( ) raw_input = self.input_client.get_user_input() content = raw_input["content"] + url = None + kwargs = {} else: time.sleep(0.5) content = user_input(timeout=timeout) diff --git a/src/agentscope/web/_app.py b/src/agentscope/web/_app.py index d5384826b..82b580bd5 100644 --- a/src/agentscope/web/_app.py +++ b/src/agentscope/web/_app.py @@ -45,8 +45,11 @@ class Message(db.Model): # type: ignore[name-defined] id = db.Column(db.Integer, primary_key=True) run_id = db.Column(db.String, db.ForeignKey("run.id"), nullable=False) name = db.Column(db.String) + role = db.Column(db.String) content = db.Column(db.String) url = db.Column(db.String) + meta = db.Column(db.String) + timestamp = db.Column(db.String) def get_history_messages(run_id: str) -> list: @@ -55,8 +58,11 @@ def get_history_messages(run_id: str) -> list: return [ { "name": message.name, + "role": message.role, "content": message.content, "url": message.url, + "metadata": json.loads(message.meta), + "timestamp": message.timestamp, } for message in messages ] @@ -140,14 +146,20 @@ def put_message() -> Response: data = request.json run_id = data["run_id"] name = data["name"] + role = data["role"] content = data["content"] + metadata = json.dumps(data["metadata"]) + timestamp = data["timestamp"] url = data.get("url", None) try: new_message = Message( run_id=run_id, name=name, + role=role, content=content, + meta=metadata, url=url, + timestamp=timestamp, ) db.session.add(new_message) db.session.commit() diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py index 6ecf1a92f..308687896 100644 --- a/src/agentscope/web/client.py +++ b/src/agentscope/web/client.py @@ -104,7 +104,7 @@ def register_run( }, timeout=10, # todo: configurable timeout ) - if resp == 200: + if resp.status_code == 200: return True else: logger.warning(f"Fail to register to studio: {resp}") @@ -113,7 +113,10 @@ def register_run( def send_message( self, name: str, + role: str, content: str, + timestamp: str = None, + metadata: dict = None, url: str = None, ) -> bool: """Send a message to the studio.""" @@ -123,8 +126,11 @@ def send_message( json={ "run_id": self.run_id, "name": name, + "role": role, "content": content, - "url": None, + "timestamp": timestamp, + "metadata": metadata, + "url": url, }, timeout=10, ) From 8f708a81986aa38b333cef8762a565d99921aeea Mon Sep 17 00:00:00 2001 From: DavdGao Date: Tue, 21 May 2024 21:47:40 +0800 Subject: [PATCH 19/80] 1. Remove the previous version of WebUI and Studio; 2. Add new framework of AgentScope Studio; --- src/agentscope/web/__init__.py | 2 +- .../web/static/css/bootstrap.min.css | 7 - src/agentscope/web/static/css/colors.css | 30 - src/agentscope/web/static/css/components.css | 132 - src/agentscope/web/static/css/home.css | 188 - src/agentscope/web/static/css/run.css | 318 - src/agentscope/web/static/css/size.css | 32 - src/agentscope/web/static/fonts/OSWALD.ttf | Bin 169108 -> 0 bytes .../web/static/htmls/agent-chat-item.html | 8 - .../web/static/htmls/user-chat-item.html | 8 - .../web/static/js/bootstrap-table.min.js | 3471 ---------- .../web/static/js/bootstrap.bundle.min.js | 2202 ------- src/agentscope/web/static/js/home.js | 187 - .../web/static/js/jquery-3.3.1.min.js | 2753 -------- src/agentscope/web/static/js/run.js | 202 - src/agentscope/web/static/js/socket.io.js | 5870 ----------------- src/agentscope/web/{ => studio}/_app.py | 15 +- src/agentscope/web/studio/constants.py | 4 - src/agentscope/web/studio/static/css/base.css | 7 + .../web/studio/static/css/clusterize.css | 38 + .../static/css/dashboard-detail-code.css | 33 + .../static/css/dashboard-detail-dialogue.css | 244 + .../studio/static/css/dashboard-detail.css | 76 + .../web/studio/static/css/dashboard-runs.css | 64 + .../web/studio/static/css/dashboard.css | 45 + .../fonts.css => studio/static/css/font.css} | 5 - .../web/studio/static/css/index.css | 193 + .../web/studio/static/css/tabulator.min.css | 2 + .../web/{ => studio}/static/fonts/KRYPTON.ttf | Bin .../studio/static/html/chat-row-template.html | 34 + .../static/html/dashboard-detail-code.html | 9 + .../html/dashboard-detail-dialogue.html | 35 + .../html/dashboard-detail-invocation.html | 3 + .../studio/static/html/dashboard-detail.html | 40 + .../studio/static/html/dashboard-runs.html | 6 + .../web/studio/static/html/dashboard.html | 8 + .../web/studio/static/html/index-guide.html | 34 + .../web/studio/static/html/market.html | 1 + .../web/studio/static/html/server.html | 1 + .../web/studio/static/html/workstation.html | 1 + .../web/studio/static/js/clusterize.min.js | 17 + .../studio/static/js/dashboard-detail-code.js | 0 .../static/js/dashboard-detail-dialogue.js | 171 + .../static/js/dashboard-detail-invocation.js | 0 .../web/studio/static/js/dashboard-detail.js | 79 + .../web/studio/static/js/dashboard-runs.js | 33 + .../web/studio/static/js/dashboard.js | 80 + src/agentscope/web/studio/static/js/index.js | 147 + .../web/studio/static/js/tabulator.min.js | 3 + .../web/studio/static/json/language.json | 34 + src/agentscope/web/studio/studio.py | 339 - .../web/studio/templates/index.html | 103 + src/agentscope/web/studio/utils.py | 220 - src/agentscope/web/templates/chat.html | 39 - src/agentscope/web/templates/home.html | 132 - src/agentscope/web/templates/run.html | 120 - 56 files changed, 1555 insertions(+), 16270 deletions(-) delete mode 100644 src/agentscope/web/static/css/bootstrap.min.css delete mode 100644 src/agentscope/web/static/css/colors.css delete mode 100644 src/agentscope/web/static/css/components.css delete mode 100644 src/agentscope/web/static/css/home.css delete mode 100644 src/agentscope/web/static/css/run.css delete mode 100644 src/agentscope/web/static/css/size.css delete mode 100644 src/agentscope/web/static/fonts/OSWALD.ttf delete mode 100644 src/agentscope/web/static/htmls/agent-chat-item.html delete mode 100644 src/agentscope/web/static/htmls/user-chat-item.html delete mode 100644 src/agentscope/web/static/js/bootstrap-table.min.js delete mode 100644 src/agentscope/web/static/js/bootstrap.bundle.min.js delete mode 100644 src/agentscope/web/static/js/home.js delete mode 100644 src/agentscope/web/static/js/jquery-3.3.1.min.js delete mode 100644 src/agentscope/web/static/js/run.js delete mode 100644 src/agentscope/web/static/js/socket.io.js rename src/agentscope/web/{ => studio}/_app.py (96%) delete mode 100644 src/agentscope/web/studio/constants.py create mode 100644 src/agentscope/web/studio/static/css/base.css create mode 100644 src/agentscope/web/studio/static/css/clusterize.css create mode 100644 src/agentscope/web/studio/static/css/dashboard-detail-code.css create mode 100644 src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css create mode 100644 src/agentscope/web/studio/static/css/dashboard-detail.css create mode 100644 src/agentscope/web/studio/static/css/dashboard-runs.css create mode 100644 src/agentscope/web/studio/static/css/dashboard.css rename src/agentscope/web/{static/css/fonts.css => studio/static/css/font.css} (50%) create mode 100644 src/agentscope/web/studio/static/css/index.css create mode 100644 src/agentscope/web/studio/static/css/tabulator.min.css rename src/agentscope/web/{ => studio}/static/fonts/KRYPTON.ttf (100%) create mode 100644 src/agentscope/web/studio/static/html/chat-row-template.html create mode 100644 src/agentscope/web/studio/static/html/dashboard-detail-code.html create mode 100644 src/agentscope/web/studio/static/html/dashboard-detail-dialogue.html create mode 100644 src/agentscope/web/studio/static/html/dashboard-detail-invocation.html create mode 100644 src/agentscope/web/studio/static/html/dashboard-detail.html create mode 100644 src/agentscope/web/studio/static/html/dashboard-runs.html create mode 100644 src/agentscope/web/studio/static/html/dashboard.html create mode 100644 src/agentscope/web/studio/static/html/index-guide.html create mode 100644 src/agentscope/web/studio/static/html/market.html create mode 100644 src/agentscope/web/studio/static/html/server.html create mode 100644 src/agentscope/web/studio/static/html/workstation.html create mode 100644 src/agentscope/web/studio/static/js/clusterize.min.js create mode 100644 src/agentscope/web/studio/static/js/dashboard-detail-code.js create mode 100644 src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js create mode 100644 src/agentscope/web/studio/static/js/dashboard-detail-invocation.js create mode 100644 src/agentscope/web/studio/static/js/dashboard-detail.js create mode 100644 src/agentscope/web/studio/static/js/dashboard-runs.js create mode 100644 src/agentscope/web/studio/static/js/dashboard.js create mode 100644 src/agentscope/web/studio/static/js/index.js create mode 100644 src/agentscope/web/studio/static/js/tabulator.min.js create mode 100644 src/agentscope/web/studio/static/json/language.json delete mode 100644 src/agentscope/web/studio/studio.py create mode 100644 src/agentscope/web/studio/templates/index.html delete mode 100644 src/agentscope/web/studio/utils.py delete mode 100644 src/agentscope/web/templates/chat.html delete mode 100644 src/agentscope/web/templates/home.html delete mode 100644 src/agentscope/web/templates/run.html diff --git a/src/agentscope/web/__init__.py b/src/agentscope/web/__init__.py index a66bc4ea8..adf88a758 100644 --- a/src/agentscope/web/__init__.py +++ b/src/agentscope/web/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """Import all modules in the web ui package.""" -from ._app import init +from agentscope.web.studio._app import init __all__ = ["init"] diff --git a/src/agentscope/web/static/css/bootstrap.min.css b/src/agentscope/web/static/css/bootstrap.min.css deleted file mode 100644 index d02c8cbca..000000000 --- a/src/agentscope/web/static/css/bootstrap.min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * Bootstrap v4.3.1 (https://getbootstrap.com/) - * Copyright 2011-2019 The Bootstrap Authors - * Copyright 2011-2019 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} -/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/agentscope/web/static/css/colors.css b/src/agentscope/web/static/css/colors.css deleted file mode 100644 index ed1ab6d80..000000000 --- a/src/agentscope/web/static/css/colors.css +++ /dev/null @@ -1,30 +0,0 @@ -:root { - /* Basic colors */ - --light-color: #c5dbea; - --media-color: #69b4d1; - --main-color: #94a1ea; - --dark-color: #2b5677; - - /* Main background colors */ - --body-bg: #ffffff; - --sidebar-bg: var(--dark-color); - - - /* General component colors */ - --border-color: var(--dark-color); - - - /* Table */ - --table-header-bg: var(--dark-color); - - - /* Sidebar */ - --sidebar-title-color: #ffffff; - --sidebar-subtitle-color: #ffffff; - --sidebar-text-color: #ffffff; - --sidebar-icon-color: #ffffff; - --sidebar-hover-bg: #d1d5e3; - - --btn-fill-color: var(--dark-color); - -} \ No newline at end of file diff --git a/src/agentscope/web/static/css/components.css b/src/agentscope/web/static/css/components.css deleted file mode 100644 index a5e495574..000000000 --- a/src/agentscope/web/static/css/components.css +++ /dev/null @@ -1,132 +0,0 @@ -body { - height: 100vh; /* Fill the entire screen height */ - width: 100vw; /* Fill the entire screen width */ - margin: 0; /* Eliminate default body margin */ - display: flex; /* Allows body to be a flex container */ - flex-direction: row; - padding: 0; - background: var(--body-bg); - font-family: oswald, sans-serif; -} - -#sidebar-title { - font-family: 'krypton', sans-serif; - height: var(--sidebar-title-height); - width: 100%; - font-size: 30px; - text-align: center; - margin-bottom: var(--sidebar-title-margin-bottom); -} - -.sidebar-subtitle { - display: flex; - align-items: center; - height: var(--sidebar-subtitle-height); - width: 100%; - margin-left: 30px; - color: var(--sidebar-subtitle-color); - font-size: var(--sidebar-font-size); - font-weight: bold; -} - -.project-list-item { - display: flex; - align-items: center; - width: calc(100% - 10px); - height: 50px; - text-align: left; - line-height: 50px; /* Make the text vertically centered */ - background: transparent; - font-size: var(--sidebar-font-size); - color: var(--sidebar-text-color); - margin-left: 10px; - padding-left: 5px; - border-radius: 5px 0 0 5px; /* left-top right-top left-bottom right-bottom */ -} - -.proj-icon { - display: flex; - align-items: center; - text-align: center; - width: 24px; - height: 24px; - margin-right: 15px; - margin-left: 15px; - fill: var(--sidebar-icon-color); - color: var(--sidebar-icon-color); - background-color: transparent; -} - -.project-list-item.selected { - background-color: var(--body-bg); - color: var(--sidebar-bg); -} - -/* When mouse is hovering */ -.project-list-item:hover { - background-color: var(--sidebar-hover-bg); -} - -.project-list-item.selected .proj-icon { - fill: var(--sidebar-bg); - color: var(--sidebar-bg); -} - -/* Set text to be unselectable */ -.text-cannot-selected { - -webkit-user-select: none; /* Safari */ - -moz-user-select: none; /* Firefox */ - -ms-user-select: none; /* IE10+/Edge */ - user-select: none; /* Standard */ -} - -/* Contact btn */ - -.share-btn { - display: flex; - align-items: center; - justify-content: center; - border: 0; - width: var(--sidebar-share-btn-length); - height: var(--sidebar-share-btn-length); - background-color: transparent; - fill: #fff; -} - -#ding-btn:hover { - fill: #1296db; -} - -#discord-btn:hover { - fill: #5865F1; -} - -#wechat-btn:hover { - fill: #69BB64; -} - -.share-btn:focus { - outline: none; -} - -#sidebar-contact { - border: var(--sidebar-contact-border) solid #ffffff; - margin: 0 var(--sidebar-contact-margin); - border-radius: 5px; - padding: var(--sidebar-contact-padding); -} - -.sidebar-row { - display: flex; - height: var(--sidebar-row-height); - justify-content: center; - width: 100%; - color: var(--sidebar-text-color); -} - -.share-img { - width: 25px; - height: 25px; - object-fit: fill; - object-position: center; -} \ No newline at end of file diff --git a/src/agentscope/web/static/css/home.css b/src/agentscope/web/static/css/home.css deleted file mode 100644 index 6f16f9d62..000000000 --- a/src/agentscope/web/static/css/home.css +++ /dev/null @@ -1,188 +0,0 @@ -:root { - /*sidebar*/ - /*Content Title*/ - - --main-content-title-total-height: var(--main-content-title-height); - - /*Custom Toolbar*/ - --custom-toolbar-height: 50px; - --custom-toolbar-margin-top: 20px; - --custom-toolbar-total-height: calc(var(--custom-toolbar-height) + var(--custom-toolbar-margin-top)); - - /*Table Container*/ - --table-container-margin-top: 10px; -} - -#sidebar { - height: var(--sidebar-height); - width: var(--sidebar-width); - top: 0; - left: 0; - bottom: 0; - position: fixed; - background-color: var(--sidebar-bg); - padding-top: var(--sidebar-padding-top); - padding-bottom: var(--sidebar-padding-bottom); - color: var(--sidebar-title-color); -} - - -#main-content { - display: flex; - flex-direction: column; - position: fixed; - margin-left: var(--main-content-margin-left); - padding: var(--main-content-padding); - height: var(--main-content-height); - width: var(--main-content-width); - background: transparent; -} - -#main-content-title { - display: flex; - align-items: center; - font-size: 25px; - height: var(--main-content-title-height); - color: var(--dark-color); -} - -#table-container { - flex-grow: 1; - overflow-y: auto; - background: #ffffff; - margin-top: var(--table-container-margin-top); - border: 1px solid var(--border-color); - height: calc(100vh - var(--main-content-title-total-height) - var(--custom-toolbar-total-height) - var(--table-container-margin-top)); - max-height: calc(100vh - var(--main-content-title-total-height) - var(--custom-toolbar-total-height) - var(--table-container-margin-top)); - border-radius: 3px; -} - -#run-table { - height: auto; - background: transparent; -} - -#run-table thead tr th { - /* Suppose there are 4 columns, each with 25% width */ - width: 25%; - text-align: left; - /* Fix table header */ - position: sticky; - top: 0; - z-index: 10; -} - -#run-table tbody tr td { - /* Suppose there are 4 columns, each with 25% width */ - width: 25%; - text-align: left; - font-size: 14px; -} - -/* Customized zebra color */ -.table-striped tbody tr:nth-of-type(odd) { - background-color: #e8e8e8; -} - -#run-table tr:hover { - background-color: var(--light-color); - transition: 0.4s; -} - -#project-list { - overflow-y: auto; - height: calc(100vh - var(--sidebar-padding-top) - var(--sidebar-title-height) - var(--sidebar-title-margin-bottom) - var(--sidebar-subtitle-height) - var(--sidebar-padding-bottom) - var(--sidebar-contact-height)); -} - -.table-header th { - background-color: var(--table-header-bg); - border: 1px solid #ffffff; - color: #ffffff; -} - -/* Disable status bar of table */ -.fixed-table-loading { - display: none !important; -} - -#custom-toolbar { - height: var(--custom-toolbar-height); - display: flex; - flex-grow: 1; /* Take up all available space */ - align-items: center; - margin-top: var(--custom-toolbar-margin-top); -} - -#customSearch { - height: 40px; - flex-grow: 1; /* Take up all available space */ - padding-left: 10px; - border: 1px solid var(--border-color); - border-radius: 3px; - color: var(--dark-color); -} - -#customSearch::placeholder { - color: var(--dark-color); -} - -#customSearch:focus { - outline-color: var(--dark-color); -} - -.toolbar-btn { - height: 40px; - width: 40px; - border: 0; - margin-left: 10px; - align-items: center; - background-color: transparent; - fill: var(--btn-fill-color); - border-radius: 5px; -} - -.toolbar-btn:hover { - background-color: var(--light-color); -} - -.toolbar-btn:focus { - outline: none; -} - -.toolbar-btn:active { - background-color: var(--dark-color); - fill: #ffffff; -} - -.toolbar-btn-img { - width: 25px; - height: 25px; - object-fit: fill; -} - - -.dropdown-menu { - text-align: center; - padding-left: 0; -} - -.dropdown-menu > div { - display: inline-block; /* Set the div to inline-block */ - text-align: left; /* Align text to left */ -} - -.dropdown-menu li { - text-align: left; - list-style-type: none; -} - -.bootstrap-table .asc { - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M5 15l7-7 7 7'/%3E%3C/svg%3E") no-repeat right center; - padding-right: 20px; /* Ensure that the text does not cover the icon */ -} - -.bootstrap-table .desc { - background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' fill='none' viewBox='0 0 24 24' stroke='%23ffffff'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M19 9l-7 7-7-7'/%3E%3C/svg%3E") no-repeat right center; - padding-right: 20px; /* Ensure that the text does not cover the icon */ -} - diff --git a/src/agentscope/web/static/css/run.css b/src/agentscope/web/static/css/run.css deleted file mode 100644 index 799fe124a..000000000 --- a/src/agentscope/web/static/css/run.css +++ /dev/null @@ -1,318 +0,0 @@ -:root { - /*dialog-content*/ - --dialog-content-height-ratio: 61.8%; - --dialog-content-main-bottom: 10px; - --dialog-content-height: 100%; - --dialog-content-width: 100%; - - /*terminal*/ - --terminal-margin-top: 10px; - --terminal-height: calc(100% - var(--dialog-content-height-ratio) - var(--terminal-margin-top)); - --terminal-width: 100%; - - /*sidebar*/ - --sidebar-info-height: 80px; - --sidebar-title-margin-top: 50px; - --sidebar-detail-height: calc(100% - var(--sidebar-title-height) - var(--sidebar-title-margin-bottom) - var(--sidebar-subtitle-height) - var(--sidebar-info-height) - var(--sidebar-title-margin-top) - var(--sidebar-subtitle-height) - var(--sidebar-contact-height)); -} - -#run-info { - display: flex; - text-align: left; /* Align text to left */ - flex-direction: column; - border: var(--sidebar-contact-border) solid #ffffff; - border-radius: 5px; - margin: 0 29px; - padding: 0 5px; - font-size: 15px; - height: var(--sidebar-info-height); - justify-content: center; -} - -#sidebar { - height: var(--sidebar-height); - width: var(--sidebar-width); - top: 0; - left: 0; - bottom: 0; - position: fixed; - background-color: var(--sidebar-bg); - padding-top: var(--sidebar-padding-top); - padding-bottom: var(--sidebar-padding-bottom); - color: var(--sidebar-title-color); -} - -.sidebar-margin-top { - margin-top: var(--sidebar-title-margin-top); -} - -#main-content { - display: flex; - flex-direction: row; - position: fixed; - margin-left: var(--main-content-margin-left); - padding: var(--main-content-padding); - height: var(--main-content-height); - width: var(--main-content-width); - background: transparent; -} - -.inner-image { - width: 70%; - height: 70%; - object-fit: contain; -} - -#terminal { - display: flex; - border: 1px solid var(--border-color); - width: 100%; - height: 400px; - background-color: var(--light-color); - font-family: monospace; - font-size: 14px; - padding: 20px; - resize: none; - overflow: auto; - border-radius: 4px; -} - -.terminal-line { - color: var(--dark-color); - white-space: pre-wrap; -} - -/* For Webkit browsers */ -/* Scrollbar */ -#terminal::-webkit-scrollbar { - background-color: transparent; - border: 0; - width: 8px; -} - -/*Scrollbar thumb*/ -#terminal::-webkit-scrollbar-thumb { - background-color: var(--dark-color); - border-radius: 4px; -} - -/*Hide scrollbar when not hovering*/ -#terminal:not(:hover)::-webkit-scrollbar-thumb { - background-color: transparent; - border: 0; -} - -/*Dialog history*/ -#dialog-panel { - display: flex; - flex-grow: 3; - flex-direction: column; - height: 100%; -} - -/*Show detailed messages*/ -#info-panel { - display: flex; - flex-grow: 2; /* Take places with dialog-panel horizontally */ - flex-direction: column; - height: 100%; - min-width: 40%; - overflow-y: auto; - margin-left: 20px; -} - -#dialog-list { - display: flex; - border: 1px solid var(--border-color); - background-color: #fff; - border-radius: 3px; - flex-direction: column; - overflow-y: auto; - height: 100%; - width: 100%; - padding: 0; - margin-bottom: 20px; -} - -.empty-dialog { - display: flex; - align-items: center; - justify-content: center; - - text-align: center; - - width: 100%; - height: 100%; - color: #b0b0b0; -} - - -#info-content { - display: flex; - flex-direction: column; - height: 100%; - width: 100%; - padding: 20px; - border-radius: 3px; - border: 1px solid var(--border-color); -} - -.user-icon { - align-self: flex-start; - width: 50px; - height: 50px; - border-radius: 10px; - margin: 10px 10px; -} - -/*bubble*/ -.chat-bubble { - max-width: 65%; /* The max width of bubble */ - min-height: 50px; /* The min height of bubble */ - position: relative; - color: var(--dark-color); - font-size: 16px; -} - -/* Adjust the bubble according to the role */ -.chat-bubble.agent { - display: flex; - flex-direction: column; - align-items: center; - background-color: var(--light-color); - border-radius: 0 10px 10px 10px; - width: fit-content; - padding: 10px; - max-width: 65%; -} - -.chat-bubble.user { - display: flex; - flex-direction: column; - align-items: start; - - background-color: var(--light-color); - border-radius: 10px 0 10px 10px; - width: fit-content; - padding: 10px; - max-width: 65%; -} - -.user-name { - font-size: 13px; -} - -.name-and-bubble { - display: flex; - flex-direction: column; - align-items: end; - flex-grow: 1; -} - -.list-group-item { - border: none !important; -} - -/*展示详细信息*/ -.info-label { - font-weight: bold; - color: #5b5b5b; -} - -.info-string { - margin-bottom: 30px; -} - -.info-block { - display: flex; - align-items: center; - margin-bottom: 30px; - background-color: #f9f9f9; - border: 1px solid #eaeaea; - border-radius: 5px; - font-family: 'Courier New', monospace; - font-size: 0.9em; - padding: 5px; - min-height: 45px; -} - -.sidebar-option { - display: flex; - align-items: center; - width: 100%; - height: 50px; - text-align: left; - line-height: 50px; /* Make the text vertically centered */ - background: var(--sidebar-bg); - font-size: var(--sidebar-font-size); - color: #000000; - margin-left: 10px; - padding-left: 5px; - border-radius: 15px 0 15px 0; -} - -#back-btn { - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - margin-left: calc((var(--sidebar-width) - 130px) / 2); - margin-right: calc((var(--sidebar-width) - 130px) / 2); - margin-top: 100px; - width: 130px; - height: 50px; - border-radius: 10px; - background-color: var(--media-color); - color: #ffffff; -} - -#back-btn:hover { - background-color: var(--sidebar-hover-bg); -} - -#back-btn:active { - color: var(--dark-color); - fill: var(--dark-color); -} - -#back-btn:active .back-svg { - fill: var(--dark-color); -} - -.back-svg { - width: 20px; - height: 20px; - object-fit: fill; - object-position: center; - border-radius: 10px; - fill: #ffffff; - margin-right: 5px; -} - -.msg-modal-data { - max-width: 300px; - max-height: 300px; - height: auto; - width: 100%; - min-width: 50px; - min-height: 50px; - margin-top: 5px; -} - -.main-content-title { - display: flex; - align-items: center; - font-size: 25px; - color: var(--dark-color); - margin-bottom: 5px; -} - -.option-list { - height: var(--sidebar-detail-height); -} - -.single-line { - white-space: nowrap; /* Prevent text wrapping */ - overflow: hidden; /* Hidden overflowed text */ - text-overflow: ellipsis; /* Using ellipsis for overflowed text */ -} \ No newline at end of file diff --git a/src/agentscope/web/static/css/size.css b/src/agentscope/web/static/css/size.css deleted file mode 100644 index 383f9f4cf..000000000 --- a/src/agentscope/web/static/css/size.css +++ /dev/null @@ -1,32 +0,0 @@ -:root { - /*Length*/ - /*Sidebar*/ - --sidebar-title-height: 50px; - --sidebar-title-margin-bottom: 50px; - --sidebar-width: 250px; - --sidebar-height: 100%; - --sidebar-padding-top: 60px; - --sidebar-padding-bottom: 60px; - --sidebar-subtitle-height: 50px; - - --sidebar-title-total-height: calc(var(--sidebar-title-height) + var(--sidebar-title-margin-bottom)); - - /*Contact*/ - --sidebar-share-btn-length: 50px; - --sidebar-contact-margin: 20px; - --sidebar-contact-padding: 10px; - --sidebar-row-height: 40px; - --sidebar-contact-border: 1px; - --sidebar-contact-height: calc(2 * (var(--sidebar-contact-padding) + var(--sidebar-contact-border) + var(--sidebar-row-height))); - - /*Main content*/ - --main-content-padding: 65px 40px 40px 40px; /* top right bottom left */ - --main-content-margin-left: var(--sidebar-width); - --main-content-margin-right: 0px; - --main-content-height: 100vh; - --main-content-width: calc(100% - var(--main-content-margin-left) - var(--main-content-margin-right)); - --main-content-title-height: 50px; - - /*Font*/ - --sidebar-font-size: 15px; -} \ No newline at end of file diff --git a/src/agentscope/web/static/fonts/OSWALD.ttf b/src/agentscope/web/static/fonts/OSWALD.ttf deleted file mode 100644 index 938e912a94e4b319f76c7fa42e288b9ce20c51d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169108 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 diff --git a/src/agentscope/web/static/htmls/agent-chat-item.html b/src/agentscope/web/static/htmls/agent-chat-item.html deleted file mode 100644 index 3d93e9e2e..000000000 --- a/src/agentscope/web/static/htmls/agent-chat-item.html +++ /dev/null @@ -1,8 +0,0 @@ - - User icon -
-
{{name}}
-
{{message}}
-
-
\ No newline at end of file diff --git a/src/agentscope/web/static/htmls/user-chat-item.html b/src/agentscope/web/static/htmls/user-chat-item.html deleted file mode 100644 index 646166b7c..000000000 --- a/src/agentscope/web/static/htmls/user-chat-item.html +++ /dev/null @@ -1,8 +0,0 @@ - -
-
{{name}}
-
{{message}}
-
- User icon -
\ No newline at end of file diff --git a/src/agentscope/web/static/js/bootstrap-table.min.js b/src/agentscope/web/static/js/bootstrap-table.min.js deleted file mode 100644 index 3d923adab..000000000 --- a/src/agentscope/web/static/js/bootstrap-table.min.js +++ /dev/null @@ -1,3471 +0,0 @@ -/** - * bootstrap-table - An extended table to integration with some of the most widely used CSS frameworks. (Supports Bootstrap, Semantic UI, Bulma, Material Design, Foundation) - * - * @version v1.18.0 - * @homepage https://bootstrap-table.com - * @author wenzhixin (http://wenzhixin.net.cn/) - * @license MIT - */ - -!function (t, e) { - "object" == typeof exports && "undefined" != typeof module ? module.exports = e(require("jquery")) : "function" == typeof define && define.amd ? define(["jquery"], e) : (t = t || self).BootstrapTable = e(t.jQuery) -}(this, (function (t) { - "use strict"; - t = t && Object.prototype.hasOwnProperty.call(t, "default") ? t.default : t; - var e = "undefined" != typeof globalThis ? globalThis : "undefined" != typeof window ? window : "undefined" != typeof global ? global : "undefined" != typeof self ? self : {}; - - function i(t, e) { - return t(e = {exports: {}}, e.exports), e.exports - } - - var n = function (t) { - return t && t.Math == Math && t - }, - o = n("object" == typeof globalThis && globalThis) || n("object" == typeof window && window) || n("object" == typeof self && self) || n("object" == typeof e && e) || Function("return this")(), - r = function (t) { - try { - return !!t() - } catch (t) { - return !0 - } - }, a = !r((function () { - return 7 != Object.defineProperty({}, "a", { - get: function () { - return 7 - } - }).a - })), s = {}.propertyIsEnumerable, l = Object.getOwnPropertyDescriptor, - c = { - f: l && !s.call({1: 2}, 1) ? function (t) { - var e = l(this, t); - return !!e && e.enumerable - } : s - }, h = function (t, e) { - return { - enumerable: !(1 & t), - configurable: !(2 & t), - writable: !(4 & t), - value: e - } - }, u = {}.toString, d = function (t) { - return u.call(t).slice(8, -1) - }, p = "".split, f = r((function () { - return !Object("z").propertyIsEnumerable(0) - })) ? function (t) { - return "String" == d(t) ? p.call(t, "") : Object(t) - } : Object, g = function (t) { - if (null == t) throw TypeError("Can't call method on " + t); - return t - }, v = function (t) { - return f(g(t)) - }, b = function (t) { - return "object" == typeof t ? null !== t : "function" == typeof t - }, y = function (t, e) { - if (!b(t)) return t; - var i, n; - if (e && "function" == typeof (i = t.toString) && !b(n = i.call(t))) return n; - if ("function" == typeof (i = t.valueOf) && !b(n = i.call(t))) return n; - if (!e && "function" == typeof (i = t.toString) && !b(n = i.call(t))) return n; - throw TypeError("Can't convert object to primitive value") - }, m = {}.hasOwnProperty, w = function (t, e) { - return m.call(t, e) - }, S = o.document, x = b(S) && b(S.createElement), k = function (t) { - return x ? S.createElement(t) : {} - }, O = !a && !r((function () { - return 7 != Object.defineProperty(k("div"), "a", { - get: function () { - return 7 - } - }).a - })), C = Object.getOwnPropertyDescriptor, P = { - f: a ? C : function (t, e) { - if (t = v(t), e = y(e, !0), O) try { - return C(t, e) - } catch (t) { - } - if (w(t, e)) return h(!c.f.call(t, e), t[e]) - } - }, T = function (t) { - if (!b(t)) throw TypeError(String(t) + " is not an object"); - return t - }, I = Object.defineProperty, A = { - f: a ? I : function (t, e, i) { - if (T(t), e = y(e, !0), T(i), O) try { - return I(t, e, i) - } catch (t) { - } - if ("get" in i || "set" in i) throw TypeError("Accessors not supported"); - return "value" in i && (t[e] = i.value), t - } - }, $ = a ? function (t, e, i) { - return A.f(t, e, h(1, i)) - } : function (t, e, i) { - return t[e] = i, t - }, E = function (t, e) { - try { - $(o, t, e) - } catch (i) { - o[t] = e - } - return e - }, R = "__core-js_shared__", j = o[R] || E(R, {}), N = Function.toString; - "function" != typeof j.inspectSource && (j.inspectSource = function (t) { - return N.call(t) - }); - var _, F, V, D = j.inspectSource, B = o.WeakMap, - L = "function" == typeof B && /native code/.test(D(B)), - H = i((function (t) { - (t.exports = function (t, e) { - return j[t] || (j[t] = void 0 !== e ? e : {}) - })("versions", []).push({ - version: "3.6.0", - mode: "global", - copyright: "© 2019 Denis Pushkarev (zloirock.ru)" - }) - })), M = 0, U = Math.random(), z = function (t) { - return "Symbol(" + String(void 0 === t ? "" : t) + ")_" + (++M + U).toString(36) - }, q = H("keys"), W = function (t) { - return q[t] || (q[t] = z(t)) - }, G = {}, K = o.WeakMap; - if (L) { - var Y = new K, J = Y.get, X = Y.has, Q = Y.set; - _ = function (t, e) { - return Q.call(Y, t, e), e - }, F = function (t) { - return J.call(Y, t) || {} - }, V = function (t) { - return X.call(Y, t) - } - } else { - var Z = W("state"); - G[Z] = !0, _ = function (t, e) { - return $(t, Z, e), e - }, F = function (t) { - return w(t, Z) ? t[Z] : {} - }, V = function (t) { - return w(t, Z) - } - } - var tt, et = { - set: _, get: F, has: V, enforce: function (t) { - return V(t) ? F(t) : _(t, {}) - }, getterFor: function (t) { - return function (e) { - var i; - if (!b(e) || (i = F(e)).type !== t) throw TypeError("Incompatible receiver, " + t + " required"); - return i - } - } - }, it = i((function (t) { - var e = et.get, i = et.enforce, n = String(String).split("String"); - (t.exports = function (t, e, r, a) { - var s = !!a && !!a.unsafe, l = !!a && !!a.enumerable, - c = !!a && !!a.noTargetGet; - "function" == typeof r && ("string" != typeof e || w(r, "name") || $(r, "name", e), i(r).source = n.join("string" == typeof e ? e : "")), t !== o ? (s ? !c && t[e] && (l = !0) : delete t[e], l ? t[e] = r : $(t, e, r)) : l ? t[e] = r : E(e, r) - })(Function.prototype, "toString", (function () { - return "function" == typeof this && e(this).source || D(this) - })) - })), nt = o, ot = function (t) { - return "function" == typeof t ? t : void 0 - }, rt = function (t, e) { - return arguments.length < 2 ? ot(nt[t]) || ot(o[t]) : nt[t] && nt[t][e] || o[t] && o[t][e] - }, at = Math.ceil, st = Math.floor, lt = function (t) { - return isNaN(t = +t) ? 0 : (t > 0 ? st : at)(t) - }, ct = Math.min, ht = function (t) { - return t > 0 ? ct(lt(t), 9007199254740991) : 0 - }, ut = Math.max, dt = Math.min, pt = function (t, e) { - var i = lt(t); - return i < 0 ? ut(i + e, 0) : dt(i, e) - }, ft = function (t) { - return function (e, i, n) { - var o, r = v(e), a = ht(r.length), s = pt(n, a); - if (t && i != i) { - for (; a > s;) if ((o = r[s++]) != o) return !0 - } else for (; a > s; s++) if ((t || s in r) && r[s] === i) return t || s || 0; - return !t && -1 - } - }, gt = {includes: ft(!0), indexOf: ft(!1)}, vt = gt.indexOf, - bt = function (t, e) { - var i, n = v(t), o = 0, r = []; - for (i in n) !w(G, i) && w(n, i) && r.push(i); - for (; e.length > o;) w(n, i = e[o++]) && (~vt(r, i) || r.push(i)); - return r - }, - yt = ["constructor", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "valueOf"], - mt = yt.concat("length", "prototype"), wt = { - f: Object.getOwnPropertyNames || function (t) { - return bt(t, mt) - } - }, St = {f: Object.getOwnPropertySymbols}, - xt = rt("Reflect", "ownKeys") || function (t) { - var e = wt.f(T(t)), i = St.f; - return i ? e.concat(i(t)) : e - }, kt = function (t, e) { - for (var i = xt(e), n = A.f, o = P.f, r = 0; r < i.length; r++) { - var a = i[r]; - w(t, a) || n(t, a, o(e, a)) - } - }, Ot = /#|\.prototype\./, Ct = function (t, e) { - var i = Tt[Pt(t)]; - return i == At || i != It && ("function" == typeof e ? r(e) : !!e) - }, Pt = Ct.normalize = function (t) { - return String(t).replace(Ot, ".").toLowerCase() - }, Tt = Ct.data = {}, It = Ct.NATIVE = "N", At = Ct.POLYFILL = "P", - $t = Ct, Et = P.f, Rt = function (t, e) { - var i, n, r, a, s, l = t.target, c = t.global, h = t.stat; - if (i = c ? o : h ? o[l] || E(l, {}) : (o[l] || {}).prototype) for (n in e) { - if (a = e[n], r = t.noTargetGet ? (s = Et(i, n)) && s.value : i[n], !$t(c ? n : l + (h ? "." : "#") + n, t.forced) && void 0 !== r) { - if (typeof a == typeof r) continue; - kt(a, r) - } - (t.sham || r && r.sham) && $(a, "sham", !0), it(i, n, a, t) - } - }, jt = !!Object.getOwnPropertySymbols && !r((function () { - return !String(Symbol()) - })), Nt = jt && !Symbol.sham && "symbol" == typeof Symbol(), - _t = Array.isArray || function (t) { - return "Array" == d(t) - }, Ft = function (t) { - return Object(g(t)) - }, Vt = Object.keys || function (t) { - return bt(t, yt) - }, Dt = a ? Object.defineProperties : function (t, e) { - T(t); - for (var i, n = Vt(e), o = n.length, r = 0; o > r;) A.f(t, i = n[r++], e[i]); - return t - }, Bt = rt("document", "documentElement"), Lt = W("IE_PROTO"), - Ht = function () { - }, Mt = function (t) { - return " + + + + + + + + + + + + + +
+
+ + + + + \ No newline at end of file diff --git a/src/agentscope/web/studio/utils.py b/src/agentscope/web/studio/utils.py deleted file mode 100644 index 36ac7f149..000000000 --- a/src/agentscope/web/studio/utils.py +++ /dev/null @@ -1,220 +0,0 @@ -# -*- coding: utf-8 -*- -"""web ui utils""" -import os -import threading -from typing import Optional -import hashlib -from multiprocessing import Queue -from queue import Empty -from collections import defaultdict - -from PIL import Image - -from dashscope.audio.asr import RecognitionCallback, Recognition - -SYS_MSG_PREFIX = "【SYSTEM】" - -thread_local_data = threading.local() - - -def init_uid_queues() -> dict: - """Initializes and returns a dictionary of user-specific queues.""" - return { - "glb_queue_chat_msg": Queue(), - "glb_queue_user_input": Queue(), - "glb_queue_reset_msg": Queue(), - } - - -glb_uid_dict = defaultdict(init_uid_queues) - - -def send_msg( - msg: str, - is_player: bool = False, - role: Optional[str] = None, - uid: Optional[str] = None, - flushing: bool = False, - avatar: Optional[str] = None, - msg_id: Optional[str] = None, -) -> None: - """Sends a message to the web UI.""" - global glb_uid_dict - glb_queue_chat_msg = glb_uid_dict[uid]["glb_queue_chat_msg"] - if is_player: - glb_queue_chat_msg.put( - [ - { - "text": msg, - "name": role, - "flushing": flushing, - "avatar": avatar, - }, - None, - ], - ) - else: - glb_queue_chat_msg.put( - [ - None, - { - "text": msg, - "name": role, - "flushing": flushing, - "avatar": avatar, - "id": msg_id, - }, - ], - ) - - -def get_chat_msg(uid: Optional[str] = None) -> list: - """Retrieves the next chat message from the queue, if available.""" - global glb_uid_dict - glb_queue_chat_msg = glb_uid_dict[uid]["glb_queue_chat_msg"] - if not glb_queue_chat_msg.empty(): - line = glb_queue_chat_msg.get(block=False) - if line is not None: - return line - return [] - - -def send_player_input(msg: str, uid: Optional[str] = None) -> None: - """Sends player input to the web UI.""" - global glb_uid_dict - glb_queue_user_input = glb_uid_dict[uid]["glb_queue_user_input"] - glb_queue_user_input.put([None, msg]) - - -def get_player_input( - timeout: Optional[int] = None, - uid: Optional[str] = None, -) -> str: - """Gets player input from the web UI or command line.""" - global glb_uid_dict - glb_queue_user_input = glb_uid_dict[uid]["glb_queue_user_input"] - - if timeout: - try: - content = glb_queue_user_input.get(block=True, timeout=timeout)[1] - except Empty as exc: - raise TimeoutError("timed out") from exc - else: - content = glb_queue_user_input.get(block=True)[1] - if content == "**Reset**": - glb_uid_dict[uid] = init_uid_queues() - raise ResetException - return content - - -def send_reset_msg(uid: Optional[str] = None) -> None: - """Sends a reset message to the web UI.""" - uid = check_uuid(uid) - global glb_uid_dict - glb_queue_reset_msg = glb_uid_dict[uid]["glb_queue_reset_msg"] - glb_queue_reset_msg.put([None, "**Reset**"]) - send_player_input("**Reset**", uid) - - -def get_reset_msg(uid: Optional[str] = None) -> None: - """Retrieves a reset message from the queue, if available.""" - global glb_uid_dict - glb_queue_reset_msg = glb_uid_dict[uid]["glb_queue_reset_msg"] - if not glb_queue_reset_msg.empty(): - content = glb_queue_reset_msg.get(block=True)[1] - if content == "**Reset**": - glb_uid_dict[uid] = init_uid_queues() - raise ResetException - - -class ResetException(Exception): - """Custom exception to signal a reset action in the application.""" - - -def check_uuid(uid: Optional[str]) -> str: - """Checks whether a UUID is provided or generates a default one.""" - if not uid or uid == "": - if os.getenv("MODELSCOPE_ENVIRONMENT") == "studio": - import gradio as gr - - raise gr.Error("Please login first") - uid = "local_user" - return uid - - -def generate_image_from_name(name: str) -> str: - """Generates an image based on the hash of the given name.""" - from agentscope.file_manager import file_manager - - # Using hashlib to generate a hash of the name - hash_func = hashlib.md5() - hash_func.update(name.encode("utf-8")) - hash_value = hash_func.hexdigest() - - # Extract the first 6 characters of the hash value as the hexadecimal - # representation of the color - # generate a color value between #000000 and #ffffff - color_hex = "#" + hash_value[:6] - color_rgb = Image.new("RGB", (1, 1), color_hex).getpixel((0, 0)) - - image_filepath = os.path.join(file_manager.dir_root, f"{name}_image.png") - - # Check if the image already exists - if os.path.exists(image_filepath): - return image_filepath - - # If the image does not exist, generate and save it - width, height = 200, 200 - image = Image.new("RGB", (width, height), color_rgb) - - image.save(image_filepath) - - return image_filepath - - -def audio2text(audio_path: str) -> str: - """Converts audio file at the given path to text using ASR.""" - # dashscope.api_key = "" - callback = RecognitionCallback() - rec = Recognition( - model="paraformer-realtime-v1", - format="wav", - sample_rate=16000, - callback=callback, - ) - - result = rec.call(audio_path) - return " ".join([s["text"] for s in result["output"]["sentence"]]) - - -def cycle_dots(text: str, num_dots: int = 3) -> str: - """display thinking dots before agent reply""" - current_dots = len(text) - len(text.rstrip(".")) - next_dots = (current_dots + 1) % (num_dots + 1) - if next_dots == 0: - next_dots = 1 - return text.rstrip(".") + "." * next_dots - - -def user_input( - prefix: str = "User input: ", - timeout: Optional[int] = None, -) -> str: - """get user input""" - if hasattr(thread_local_data, "uid"): - get_reset_msg(uid=thread_local_data.uid) - content = get_player_input( - timeout=timeout, - uid=thread_local_data.uid, - ) - else: - if timeout: - from inputimeout import inputimeout, TimeoutOccurred - - try: - content = inputimeout(prefix, timeout=timeout) - except TimeoutOccurred as exc: - raise TimeoutError("timed out") from exc - else: - content = input(prefix) - return content diff --git a/src/agentscope/web/templates/chat.html b/src/agentscope/web/templates/chat.html deleted file mode 100644 index a504ed503..000000000 --- a/src/agentscope/web/templates/chat.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - Chat - - - - -
    - {% for message in messages %} -
  • {{ message.name }}: {{ message.content }}
  • - {% endfor %} -
- - - - diff --git a/src/agentscope/web/templates/home.html b/src/agentscope/web/templates/home.html deleted file mode 100644 index 5765b1257..000000000 --- a/src/agentscope/web/templates/home.html +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - AgentScope - - - - - - - - - - - - -
-
Runs
-
- - -
- - -
-
- - -
- - - - - - - - - - - - -
IDNAME - PROJECT - - TIMESTAMP -
-
-
- - - - - - - - - - \ No newline at end of file diff --git a/src/agentscope/web/templates/run.html b/src/agentscope/web/templates/run.html deleted file mode 100644 index 8a3cffcff..000000000 --- a/src/agentscope/web/templates/run.html +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - AgentScope - - - - - - - - - - - - -
-
-
Conversation
-
-
Logging
-
-
- -
-
Message
-
-
-
- - - - - - - - - - \ No newline at end of file From f7766416092d1dd8d089082734b091d8855823dc Mon Sep 17 00:00:00 2001 From: DavdGao Date: Tue, 21 May 2024 21:50:42 +0800 Subject: [PATCH 20/80] reformat --- src/agentscope/web/studio/_app.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index 2339737cc..5a5781fbe 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -4,8 +4,15 @@ import os from datetime import datetime -from flask import Flask, request, jsonify, render_template, Response, abort, \ - send_file +from flask import ( + Flask, + request, + jsonify, + render_template, + Response, + abort, + send_file, +) from flask_cors import CORS from flask_sqlalchemy import SQLAlchemy from flask_socketio import SocketIO, join_room, leave_room @@ -193,7 +200,8 @@ def get_all_runs() -> list: @app.route("/file") -def get_local_file(): +def get_local_file() -> Response: + """Get the local file via the url.""" file_path = request.args.get("url") try: file = send_file(file_path) From 54649949d3b8ab869e00a005f76347dae542d3b1 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 22 May 2024 11:39:30 +0800 Subject: [PATCH 21/80] rename old studio to gradio --- setup.py | 2 +- src/agentscope/agents/user_agent.py | 2 +- src/agentscope/utils/logging_utils.py | 2 +- src/agentscope/web/gradio/__init__.py | 0 src/agentscope/web/gradio/app.py | 28 ++ src/agentscope/web/gradio/constants.py | 4 + src/agentscope/web/gradio/studio.py | 339 +++++++++++++++++++++++++ src/agentscope/web/gradio/utils.py | 220 ++++++++++++++++ 8 files changed, 594 insertions(+), 3 deletions(-) create mode 100644 src/agentscope/web/gradio/__init__.py create mode 100644 src/agentscope/web/gradio/app.py create mode 100644 src/agentscope/web/gradio/constants.py create mode 100644 src/agentscope/web/gradio/studio.py create mode 100644 src/agentscope/web/gradio/utils.py diff --git a/setup.py b/setup.py index ee22169f4..98b978183 100644 --- a/setup.py +++ b/setup.py @@ -121,7 +121,7 @@ python_requires=">=3.9", entry_points={ "console_scripts": [ - "as_studio=agentscope.web.studio.studio:run_app", + "as_studio=agentscope.web.gradio.studio:run_app", "as_workflow=agentscope.web.workstation.workflow:main", "as_server=agentscope.server.launcher:as_server", ], diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index 171766ef0..71c403ee0 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -8,7 +8,7 @@ from agentscope.agents import AgentBase from agentscope._runtime import _runtime from agentscope.message import Msg -from agentscope.web.studio.utils import user_input +from agentscope.web.gradio.utils import user_input class UserAgent(AgentBase): diff --git a/src/agentscope/utils/logging_utils.py b/src/agentscope/utils/logging_utils.py index e5de7b84f..4eb68673e 100644 --- a/src/agentscope/utils/logging_utils.py +++ b/src/agentscope/utils/logging_utils.py @@ -7,7 +7,7 @@ from loguru import logger -from agentscope.web.studio.utils import ( +from agentscope.web.gradio.utils import ( generate_image_from_name, send_msg, get_reset_msg, diff --git a/src/agentscope/web/gradio/__init__.py b/src/agentscope/web/gradio/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/agentscope/web/gradio/app.py b/src/agentscope/web/gradio/app.py new file mode 100644 index 000000000..b074696b4 --- /dev/null +++ b/src/agentscope/web/gradio/app.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +"""run agentscope studio server""" +import argparse +import os +import sys +import threading +import time +from collections import defaultdict +from typing import Optional, Callable +import traceback + +from flask import Flask, request, jsonify, render_template, Response +from flask_cors import CORS +from flask_socketio import SocketIO + +app = Flask(__name__) +CORS(app) +socketio = SocketIO(app) + + +@app.route("/", methods=["GET"]) +def index() -> Response: + """_summary_ + + Returns: + Response: _description_ + """ + return render_template("index.html") diff --git a/src/agentscope/web/gradio/constants.py b/src/agentscope/web/gradio/constants.py new file mode 100644 index 000000000..0d593113b --- /dev/null +++ b/src/agentscope/web/gradio/constants.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +""" Some constants used in the AS studio""" + +_SPEAK = "**speak**" diff --git a/src/agentscope/web/gradio/studio.py b/src/agentscope/web/gradio/studio.py new file mode 100644 index 000000000..1ff9f6ee2 --- /dev/null +++ b/src/agentscope/web/gradio/studio.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +"""run web ui""" +import argparse +import os +import sys +import threading +import time +from collections import defaultdict +from typing import Optional, Callable +import traceback + +try: + import gradio as gr +except ImportError: + gr = None + +try: + import modelscope_studio as mgr +except ImportError: + mgr = None + +from agentscope.web.gradio.utils import ( + send_player_input, + get_chat_msg, + SYS_MSG_PREFIX, + ResetException, + check_uuid, + send_msg, + generate_image_from_name, + audio2text, + send_reset_msg, + thread_local_data, + cycle_dots, +) +from agentscope.web.gradio.constants import _SPEAK + +MAX_NUM_DISPLAY_MSG = 20 +FAIL_COUNT_DOWN = 30 + + +def init_uid_list() -> list: + """Initialize an empty list for storing user IDs.""" + return [] + + +glb_history_dict = defaultdict(init_uid_list) +glb_doing_signal_dict = defaultdict(init_uid_list) +glb_signed_user = [] + + +def reset_glb_var(uid: str) -> None: + """Reset global variables for a given user ID.""" + global glb_history_dict + global glb_doing_signal_dict + glb_history_dict[uid] = init_uid_list() + glb_doing_signal_dict[uid] = init_uid_list() + + +def get_chat(uid: str) -> list[list]: + """Retrieve chat messages for a given user ID.""" + uid = check_uuid(uid) + global glb_history_dict + global glb_doing_signal_dict + + line = get_chat_msg(uid=uid) + # TODO: Optimize the display effect, currently there is a problem of + # output display jumping + if line: + if line[1] and line[1]["text"] == _SPEAK: + line[1]["text"] = "" + glb_doing_signal_dict[uid] = line + else: + glb_history_dict[uid] += [line] + glb_doing_signal_dict[uid] = [] + dial_msg = [] + for line in glb_history_dict[uid]: + _, msg = line + if isinstance(msg, dict): + dial_msg.append(line) + else: + # User chat, format: (msg, None) + dial_msg.append(line) + if glb_doing_signal_dict[uid]: + if glb_doing_signal_dict[uid][1]: + text = cycle_dots(glb_doing_signal_dict[uid][1]["text"]) + glb_doing_signal_dict[uid][1]["text"] = text + glb_doing_signal_dict[uid][1]["id"] = str(time.time()) + glb_doing_signal_dict[uid][1]["flushing"] = False + + dial_msg.append(glb_doing_signal_dict[uid]) + return dial_msg[-MAX_NUM_DISPLAY_MSG:] + + +def send_audio(audio_term: str, uid: str) -> None: + """Convert audio input to text and send as a chat message.""" + uid = check_uuid(uid) + content = audio2text(audio_path=audio_term) + send_player_input(content, uid=uid) + msg = f"""{content} + """ + send_msg(msg, is_player=True, role="Me", uid=uid, avatar=None) + + +def send_image(image_term: str, uid: str) -> None: + """Send an image as a chat message.""" + uid = check_uuid(uid) + send_player_input(image_term, uid=uid) + + msg = f"""""" + avatar = generate_image_from_name("Me") + send_msg(msg, is_player=True, role="Me", uid=uid, avatar=avatar) + + +def send_message(msg: str, uid: str) -> str: + """Send a generic message to the player.""" + uid = check_uuid(uid) + send_player_input(msg, uid=uid) + avatar = generate_image_from_name("Me") + send_msg(msg, is_player=True, role="Me", uid=uid, avatar=avatar) + return "" + + +def fn_choice(data: gr.EventData, uid: str) -> None: + """Handle a selection event from the chatbot interface.""" + uid = check_uuid(uid) + # pylint: disable=protected-access + send_player_input(data._data["value"], uid=uid) + + +def import_function_from_path( + module_path: str, + function_name: str, + module_name: Optional[str] = None, +) -> Callable: + """Import a function from the given module path.""" + import importlib.util + + script_dir = os.path.dirname(os.path.abspath(module_path)) + + # Temporarily add a script directory to sys.path + original_sys_path = sys.path[:] + sys.path.insert(0, script_dir) + + try: + # If a module name is not provided, you can use the filename ( + # without extension) as the module name + if module_name is None: + module_name = os.path.splitext(os.path.basename(module_path))[0] + # Creating module specifications and loading modules + spec = importlib.util.spec_from_file_location( + module_name, + module_path, + ) + if spec is not None: + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + # Getting a function from a module + function = getattr(module, function_name) + else: + raise ImportError( + f"Could not find module spec for {module_name} at" + f" {module_path}", + ) + except AttributeError as exc: + raise AttributeError( + f"The module '{module_name}' does not have a function named '" + f"{function_name}'. Please put your code in the main function, " + f"read README.md for details.", + ) from exc + finally: + # Restore the original sys.path + sys.path = original_sys_path + + return function + + +# pylint: disable=too-many-statements +def run_app() -> None: + """Entry point for the web UI application.""" + assert gr is not None, "Please install [full] version of AgentScope." + + parser = argparse.ArgumentParser() + parser.add_argument("script", type=str, help="Script file to run") + args = parser.parse_args() + + # Make sure script_path is an absolute path + script_path = os.path.abspath(args.script) + + # Get the directory where the script is located + script_dir = os.path.dirname(script_path) + # Save the current working directory + # Change the current working directory to the directory where + os.chdir(script_dir) + + def start_game(uid: str) -> None: + """Start the main game loop.""" + thread_local_data.uid = uid + if script_path.endswith(".py"): + main = import_function_from_path(script_path, "main") + elif script_path.endswith(".json"): + from agentscope.web.workstation.workflow import ( + start_workflow, + load_config, + ) + + config = load_config(script_path) + main = lambda: start_workflow(config) + else: + raise ValueError(f"Unrecognized file formats: {script_path}") + + while True: + try: + main() + except ResetException: + print(f"Reset Successfully:{uid} ") + except Exception as e: + trace_info = "".join( + traceback.TracebackException.from_exception(e).format(), + ) + for i in range(FAIL_COUNT_DOWN, 0, -1): + send_msg( + f"{SYS_MSG_PREFIX} error {trace_info}, reboot " + f"in {i} seconds", + uid=uid, + ) + time.sleep(1) + reset_glb_var(uid) + + def check_for_new_session(uid: str) -> None: + """ + Check for a new user session and start a game thread if necessary. + """ + uid = check_uuid(uid) + if uid not in glb_signed_user: + glb_signed_user.append(uid) + print("==========Signed User==========") + print(f"Total number of users: {len(glb_signed_user)}") + run_thread = threading.Thread( + target=start_game, + args=(uid,), + ) + run_thread.start() + + with gr.Blocks() as demo: + warning_html_code = """ +
+

If you want to start over, please click the + reset + button and refresh the page

+
+ """ + gr.HTML(warning_html_code) + uuid = gr.Textbox(label="modelscope_uuid", visible=False) + + with gr.Row(): + chatbot = mgr.Chatbot( + label="Dialog", + show_label=False, + bubble_full_width=False, + visible=True, + ) + + with gr.Column(): + user_chat_input = gr.Textbox( + label="user_chat_input", + placeholder="Say something here", + show_label=False, + ) + send_button = gr.Button(value="📣Send") + with gr.Row(): + audio = gr.Accordion("Audio input", open=False) + with audio: + audio_term = gr.Audio( + visible=True, + type="filepath", + format="wav", + ) + submit_audio_button = gr.Button(value="Send Audio") + image = gr.Accordion("Image input", open=False) + with image: + image_term = gr.Image( + visible=True, + height=300, + interactive=True, + type="filepath", + ) + submit_image_button = gr.Button(value="Send Image") + with gr.Column(): + reset_button = gr.Button(value="Reset") + + # submit message + send_button.click( + send_message, + [user_chat_input, uuid], + user_chat_input, + ) + user_chat_input.submit( + send_message, + [user_chat_input, uuid], + user_chat_input, + ) + + submit_audio_button.click( + send_audio, + inputs=[audio_term, uuid], + outputs=[audio_term], + ) + + submit_image_button.click( + send_image, + inputs=[image_term, uuid], + outputs=[image_term], + ) + + reset_button.click(send_reset_msg, inputs=[uuid]) + + chatbot.custom(fn=fn_choice, inputs=[uuid]) + + demo.load( + check_for_new_session, + inputs=[uuid], + every=0.5, + ) + + demo.load( + get_chat, + inputs=[uuid], + outputs=[chatbot], + every=0.5, + ) + demo.queue() + demo.launch() + + +if __name__ == "__main__": + run_app() diff --git a/src/agentscope/web/gradio/utils.py b/src/agentscope/web/gradio/utils.py new file mode 100644 index 000000000..36ac7f149 --- /dev/null +++ b/src/agentscope/web/gradio/utils.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +"""web ui utils""" +import os +import threading +from typing import Optional +import hashlib +from multiprocessing import Queue +from queue import Empty +from collections import defaultdict + +from PIL import Image + +from dashscope.audio.asr import RecognitionCallback, Recognition + +SYS_MSG_PREFIX = "【SYSTEM】" + +thread_local_data = threading.local() + + +def init_uid_queues() -> dict: + """Initializes and returns a dictionary of user-specific queues.""" + return { + "glb_queue_chat_msg": Queue(), + "glb_queue_user_input": Queue(), + "glb_queue_reset_msg": Queue(), + } + + +glb_uid_dict = defaultdict(init_uid_queues) + + +def send_msg( + msg: str, + is_player: bool = False, + role: Optional[str] = None, + uid: Optional[str] = None, + flushing: bool = False, + avatar: Optional[str] = None, + msg_id: Optional[str] = None, +) -> None: + """Sends a message to the web UI.""" + global glb_uid_dict + glb_queue_chat_msg = glb_uid_dict[uid]["glb_queue_chat_msg"] + if is_player: + glb_queue_chat_msg.put( + [ + { + "text": msg, + "name": role, + "flushing": flushing, + "avatar": avatar, + }, + None, + ], + ) + else: + glb_queue_chat_msg.put( + [ + None, + { + "text": msg, + "name": role, + "flushing": flushing, + "avatar": avatar, + "id": msg_id, + }, + ], + ) + + +def get_chat_msg(uid: Optional[str] = None) -> list: + """Retrieves the next chat message from the queue, if available.""" + global glb_uid_dict + glb_queue_chat_msg = glb_uid_dict[uid]["glb_queue_chat_msg"] + if not glb_queue_chat_msg.empty(): + line = glb_queue_chat_msg.get(block=False) + if line is not None: + return line + return [] + + +def send_player_input(msg: str, uid: Optional[str] = None) -> None: + """Sends player input to the web UI.""" + global glb_uid_dict + glb_queue_user_input = glb_uid_dict[uid]["glb_queue_user_input"] + glb_queue_user_input.put([None, msg]) + + +def get_player_input( + timeout: Optional[int] = None, + uid: Optional[str] = None, +) -> str: + """Gets player input from the web UI or command line.""" + global glb_uid_dict + glb_queue_user_input = glb_uid_dict[uid]["glb_queue_user_input"] + + if timeout: + try: + content = glb_queue_user_input.get(block=True, timeout=timeout)[1] + except Empty as exc: + raise TimeoutError("timed out") from exc + else: + content = glb_queue_user_input.get(block=True)[1] + if content == "**Reset**": + glb_uid_dict[uid] = init_uid_queues() + raise ResetException + return content + + +def send_reset_msg(uid: Optional[str] = None) -> None: + """Sends a reset message to the web UI.""" + uid = check_uuid(uid) + global glb_uid_dict + glb_queue_reset_msg = glb_uid_dict[uid]["glb_queue_reset_msg"] + glb_queue_reset_msg.put([None, "**Reset**"]) + send_player_input("**Reset**", uid) + + +def get_reset_msg(uid: Optional[str] = None) -> None: + """Retrieves a reset message from the queue, if available.""" + global glb_uid_dict + glb_queue_reset_msg = glb_uid_dict[uid]["glb_queue_reset_msg"] + if not glb_queue_reset_msg.empty(): + content = glb_queue_reset_msg.get(block=True)[1] + if content == "**Reset**": + glb_uid_dict[uid] = init_uid_queues() + raise ResetException + + +class ResetException(Exception): + """Custom exception to signal a reset action in the application.""" + + +def check_uuid(uid: Optional[str]) -> str: + """Checks whether a UUID is provided or generates a default one.""" + if not uid or uid == "": + if os.getenv("MODELSCOPE_ENVIRONMENT") == "studio": + import gradio as gr + + raise gr.Error("Please login first") + uid = "local_user" + return uid + + +def generate_image_from_name(name: str) -> str: + """Generates an image based on the hash of the given name.""" + from agentscope.file_manager import file_manager + + # Using hashlib to generate a hash of the name + hash_func = hashlib.md5() + hash_func.update(name.encode("utf-8")) + hash_value = hash_func.hexdigest() + + # Extract the first 6 characters of the hash value as the hexadecimal + # representation of the color + # generate a color value between #000000 and #ffffff + color_hex = "#" + hash_value[:6] + color_rgb = Image.new("RGB", (1, 1), color_hex).getpixel((0, 0)) + + image_filepath = os.path.join(file_manager.dir_root, f"{name}_image.png") + + # Check if the image already exists + if os.path.exists(image_filepath): + return image_filepath + + # If the image does not exist, generate and save it + width, height = 200, 200 + image = Image.new("RGB", (width, height), color_rgb) + + image.save(image_filepath) + + return image_filepath + + +def audio2text(audio_path: str) -> str: + """Converts audio file at the given path to text using ASR.""" + # dashscope.api_key = "" + callback = RecognitionCallback() + rec = Recognition( + model="paraformer-realtime-v1", + format="wav", + sample_rate=16000, + callback=callback, + ) + + result = rec.call(audio_path) + return " ".join([s["text"] for s in result["output"]["sentence"]]) + + +def cycle_dots(text: str, num_dots: int = 3) -> str: + """display thinking dots before agent reply""" + current_dots = len(text) - len(text.rstrip(".")) + next_dots = (current_dots + 1) % (num_dots + 1) + if next_dots == 0: + next_dots = 1 + return text.rstrip(".") + "." * next_dots + + +def user_input( + prefix: str = "User input: ", + timeout: Optional[int] = None, +) -> str: + """get user input""" + if hasattr(thread_local_data, "uid"): + get_reset_msg(uid=thread_local_data.uid) + content = get_player_input( + timeout=timeout, + uid=thread_local_data.uid, + ) + else: + if timeout: + from inputimeout import inputimeout, TimeoutOccurred + + try: + content = inputimeout(prefix, timeout=timeout) + except TimeoutOccurred as exc: + raise TimeoutError("timed out") from exc + else: + content = input(prefix) + return content From 6982c89a5ec6736984e0cf2b1de48c8661998ddc Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 22 May 2024 12:30:32 +0800 Subject: [PATCH 22/80] add api to get available run_id --- src/agentscope/web/studio/_app.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index 5a5781fbe..c081b8d2f 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -2,6 +2,7 @@ """The main entry point of the web UI.""" import json import os +import uuid from datetime import datetime from flask import ( @@ -146,7 +147,7 @@ def register_server() -> Response: return jsonify(status="ok", msg="") -@app.route("/api/message/put", methods=["POST"]) +@app.route("/api/messages/put", methods=["POST"]) def put_message() -> Response: """ Used by the application to speak a message to the Hub. @@ -187,18 +188,24 @@ def put_message() -> Response: return jsonify(status="ok", msg="") -@app.route("/api/messages/", methods=["GET"]) +@app.route("/api/messages/run/", methods=["GET"]) def get_messages(run_id: str) -> list: """Get the history messages of specific run_id.""" return get_history_messages(run_id=run_id) -@app.route("/api/runs", methods=["GET"]) +@app.route("/api/runs/all", methods=["GET"]) def get_all_runs() -> list: """Get all runs.""" return get_runs() +@app.route("/api/runs/new", methods=["GET"]) +def get_available_run_id() -> Response: + """Get an available run id.""" + return jsonify({"run_id": uuid.uuid4().hex}) + + @app.route("/file") def get_local_file() -> Response: """Get the local file via the url.""" From 88aa82067bb6d8da9e405a7c90e75e55f9c38e3f Mon Sep 17 00:00:00 2001 From: DavdGao Date: Wed, 22 May 2024 15:10:58 +0800 Subject: [PATCH 23/80] 1. Rename market to gallery; 2. Add main and base color; 3. Add discord, dingding and github link in index.html --- src/agentscope/web/studio/static/css/base.css | 16 ++++++ .../static/css/dashboard-detail-code.css | 4 +- .../static/css/dashboard-detail-dialogue.css | 14 +++-- .../studio/static/css/dashboard-detail.css | 26 +++++---- .../web/studio/static/css/dashboard-runs.css | 3 +- .../web/studio/static/css/dashboard.css | 2 +- .../web/studio/static/css/index.css | 55 +++++++++++-------- .../web/studio/static/html/index-guide.html | 2 +- .../web/studio/templates/index.html | 43 +++++++++++++-- 9 files changed, 118 insertions(+), 47 deletions(-) diff --git a/src/agentscope/web/studio/static/css/base.css b/src/agentscope/web/studio/static/css/base.css index 458255c23..248f3e8d0 100644 --- a/src/agentscope/web/studio/static/css/base.css +++ b/src/agentscope/web/studio/static/css/base.css @@ -1,7 +1,23 @@ +:root { + --tab-btn-icon-length: 20px; + + --main-color: #59AC80; + /*--main-color-light: #7DC3A2;*/ + --main-color-light: #A0D9C4; + --main-color-dark: #3D7D5A; + --base-color: #F3F9F1; + + --border-color: #EBEBF0; +} + /*Text cannot be selected*/ .unselectable-text { -webkit-user-select: none; /* Safari */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE10+/Edge */ user-select: none; /* Standard */ +} + +a { + text-decoration: none; } \ No newline at end of file diff --git a/src/agentscope/web/studio/static/css/dashboard-detail-code.css b/src/agentscope/web/studio/static/css/dashboard-detail-code.css index 610f8f8f5..91623552c 100644 --- a/src/agentscope/web/studio/static/css/dashboard-detail-code.css +++ b/src/agentscope/web/studio/static/css/dashboard-detail-code.css @@ -18,7 +18,7 @@ height: 100%; width: var(--code-list-width); flex-direction: column; - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); box-sizing: border-box; margin-right: 50px; } @@ -29,5 +29,5 @@ width: var(--code-content-width); height: 100%; box-sizing: border-box; - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); } \ No newline at end of file diff --git a/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css b/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css index f40d6dfa8..977fa6fb1 100644 --- a/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css +++ b/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css @@ -34,7 +34,7 @@ max-height: 100%; width: var(--chat-detail-width); box-sizing: border-box; - border-left: 1px solid #e1e1e1; + border-left: 1px solid var(--border-color); padding: 50px 20px; } @@ -59,7 +59,7 @@ .dialogue-info-value { display: flex; - background-color: #e1e1e1; + background-color: var(--border-color); height: fit-content; width: 100%; align-items: center; @@ -88,7 +88,7 @@ margin-bottom: 50px; padding: 5px; max-height: 100%; - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); border-radius: 8px; } @@ -109,6 +109,10 @@ margin: 10px 0; } +.chat-row:hover { + background-color: var(--main-color-light); +} + .user.chat-row { justify-content: flex-end; } @@ -151,7 +155,7 @@ .chat-bubble { display: flex; flex-direction: column; - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); flex-grow: 1; box-sizing: border-box; padding: 10px; @@ -186,7 +190,7 @@ height: 10%; width: 100%; box-sizing: border-box; - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); border-radius: 8px; flex-direction: column; padding: 10px; diff --git a/src/agentscope/web/studio/static/css/dashboard-detail.css b/src/agentscope/web/studio/static/css/dashboard-detail.css index a000fcd3d..6d1950fc1 100644 --- a/src/agentscope/web/studio/static/css/dashboard-detail.css +++ b/src/agentscope/web/studio/static/css/dashboard-detail.css @@ -19,7 +19,7 @@ width: var(--detail-sidebar-width); background-color: #ffffff; - border-right-color: #e1e1e1; + border-right-color: var(--border-color); border-right-width: 1px; border-right-style: solid; padding: var(--detail-sidebar-padding-vertical) 0; @@ -30,14 +30,16 @@ flex-direction: column; width: 100%; height: fit-content; - padding-left: 10px; + padding: 50px 10px; + box-sizing: border-box; } .detail-sidebar-tab { display: flex; flex-direction: row; - height: 50px; + height: var(--navigation-bar-item-height); width: 100%; + box-sizing: border-box; align-items: center; border-radius: 10px; padding-left: 20px; @@ -45,23 +47,27 @@ } .detail-sidebar-tab:hover { - background-color: #6b2626; + background-color: var(--main-color-light); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab:active { - background-color: #1f5e9b; + background-color: var(--main-color-dark); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab.selected { - background-color: #1f5e9b; - fill: #ffffff; - color: #ffffff; + background-color: var(--main-color); + fill: var(--base-color); + color: var(--base-color); } .detail-sidebar-tab-svg { display: flex; - width: 20px; - height: 20px; + width: var(--tab-btn-icon-length); + height: var(--tab-btn-icon-length); margin-right: 10px; } diff --git a/src/agentscope/web/studio/static/css/dashboard-runs.css b/src/agentscope/web/studio/static/css/dashboard-runs.css index 7db48bf76..70695bbf7 100644 --- a/src/agentscope/web/studio/static/css/dashboard-runs.css +++ b/src/agentscope/web/studio/static/css/dashboard-runs.css @@ -30,7 +30,7 @@ width: 100%; height: var(--runs-table-height); max-height: var(--runs-table-height); - border: 1px solid #e1e1e1; + border: 1px solid var(--border-color); background: transparent; } @@ -39,6 +39,7 @@ height: var(--runs-search-input-height); flex-grow: 1; align-items: center; + border: 1px solid var(--border-color); } /* Remove border from tabulator */ diff --git a/src/agentscope/web/studio/static/css/dashboard.css b/src/agentscope/web/studio/static/css/dashboard.css index adef7cd85..034d1ee6f 100644 --- a/src/agentscope/web/studio/static/css/dashboard.css +++ b/src/agentscope/web/studio/static/css/dashboard.css @@ -16,7 +16,7 @@ height: var(--dashboard-titlebar-height); flex-direction: row; align-items: center; - border-bottom: 1px solid #e1e1e1; + border-bottom: 1px solid var(--border-color); box-sizing: border-box; padding: 0 20px; } diff --git a/src/agentscope/web/studio/static/css/index.css b/src/agentscope/web/studio/static/css/index.css index e294157ce..5f0f780d8 100644 --- a/src/agentscope/web/studio/static/css/index.css +++ b/src/agentscope/web/studio/static/css/index.css @@ -2,10 +2,16 @@ --navigation-bar-background-color: #ffffff; --logo-font-color: #000000; --tab-font-color: #000000; - --navigation-bar-border-color: #e1e1e1; - --navigation-bar-width: 250px; + --navigation-bar-width: 240px; --content-width: calc(100% - var(--navigation-bar-width)); + + /*To ensure the collapsed navigation bar fit the icon*/ + --navigation-bar-item-icon-length: var(--tab-btn-icon-length); + --navigation-bar-item-icon-margin: 10px; + --navigation-bar-item-padding: 10px; + --navigation-bar-width-collapsed: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin) + 2 * var(--navigation-bar-item-padding)); + --navigation-bar-item-height: calc(var(--navigation-bar-item-icon-length) + 2 * var(--navigation-bar-item-icon-margin)) } html { @@ -23,7 +29,7 @@ body { flex-direction: row; padding: 0; background: var(--body-bg); - font-family: oswald, sans-serif; + font-family: sans-serif; box-sizing: border-box; } @@ -34,17 +40,17 @@ body { height: 100%; justify-content: space-between; align-items: center; - padding: 0; - + padding: 50px 0 50px 0; + box-sizing: border-box; background-color: var(--navigation-bar-background-color); - border-right: 1px solid var(--navigation-bar-border-color); + border-right: 1px solid var(--border-color); /*Animation*/ transition: width 0.5s; } #navigation-bar.collapsed { - width: 65px; + width: var(--navigation-bar-width-collapsed); } .navigation-bar-item-label { @@ -79,7 +85,7 @@ body { height: 150px; width: 500px; min-width: 200px; - background-color: #1c6cc2; + background-color: var(--main-color); border-radius: 10px; justify-content: flex-end; padding: 25px; @@ -117,6 +123,7 @@ body { font-size: 25px; font-weight: bold; } + .guide-block-detail { display: flex; height: fit-content; @@ -130,7 +137,6 @@ body { font-family: 'krypton', sans-serif; font-size: 20px; color: var(--logo-font-color); - margin-top: 50px; white-space: nowrap; } @@ -145,7 +151,7 @@ body { width: 100%; flex-direction: column; - padding: 10px; + padding: var(--navigation-bar-item-padding); box-sizing: border-box; } @@ -155,7 +161,7 @@ body { align-items: center; background-color: var(--navigation-bar-background-color); width: 100%; - height: 45px; + height: var(--navigation-bar-item-height); border-radius: 10px; color: var(--tab-font-color); box-sizing: border-box; @@ -163,31 +169,36 @@ body { } .navigation-bar-item:hover { - background-color: blue; + background-color: var(--main-color-light); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item:active { - background-color: #4d4d4d; + background-color: var(--main-color-dark); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item.selected { - background-color: #8c8c8c; + background-color: var(--main-color); + fill: var(--base-color); + color: var(--base-color); } .navigation-bar-item-icon { display: flex; - width: 25px; - height: 25px; - fill: #000000; - margin: 0 10px; - min-height: 25px; - min-width: 25px; + width: var(--navigation-bar-item-icon-length); + height: var(--navigation-bar-item-icon-length); + margin: 0 var(--navigation-bar-item-icon-margin); + min-height: var(--navigation-bar-item-icon-length); + min-width: var(--navigation-bar-item-icon-length); } .navigation-bar-hr { display: flex; height: 1px; - background-color: #e1e1e1; /* 设置分隔线的颜色 */ - border: none; /* 移除默认的边框 */ + background-color: var(--border-color); + border: none; margin: 10px 0; } \ No newline at end of file diff --git a/src/agentscope/web/studio/static/html/index-guide.html b/src/agentscope/web/studio/static/html/index-guide.html index 2284c2efc..5826720bf 100644 --- a/src/agentscope/web/studio/static/html/index-guide.html +++ b/src/agentscope/web/studio/static/html/index-guide.html @@ -23,7 +23,7 @@

Start to explore AgentScope Studio with

-
Market
+
Gallery
Coming soon ...
diff --git a/src/agentscope/web/studio/templates/index.html b/src/agentscope/web/studio/templates/index.html index 95eca5ebd..5bc2ca670 100644 --- a/src/agentscope/web/studio/templates/index.html +++ b/src/agentscope/web/studio/templates/index.html @@ -16,7 +16,7 @@ - + From 294bffe7ba49d8a6dea61e790465c1b66832558c Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 22 May 2024 15:33:23 +0800 Subject: [PATCH 24/80] update apis --- src/agentscope/web/client.py | 2 +- .../web/studio/static/js/dashboard-detail-dialogue.js | 2 +- src/agentscope/web/studio/static/js/dashboard-runs.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py index 308687896..71a888a52 100644 --- a/src/agentscope/web/client.py +++ b/src/agentscope/web/client.py @@ -120,7 +120,7 @@ def send_message( url: str = None, ) -> bool: """Send a message to the studio.""" - url = f"{self.studio_url}/api/message/put" + url = f"{self.studio_url}/api/messages/put" resp = requests.post( url, json={ diff --git a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js index f0cbf8295..1c4f4725c 100644 --- a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js @@ -129,7 +129,7 @@ async function initializeDashboardDetailDialoguePage(pRuntimeInfo) { await loadChatTemplate(); // Fetch the chat history from backend - fetch("/api/messages/" + pRuntimeInfo.id) + fetch("/api/messages/run/" + pRuntimeInfo.id) .then(response => { if (!response.ok) { throw new Error('Failed to fetch messages data'); diff --git a/src/agentscope/web/studio/static/js/dashboard-runs.js b/src/agentscope/web/studio/static/js/dashboard-runs.js index 9953f6fb2..2087fdc52 100644 --- a/src/agentscope/web/studio/static/js/dashboard-runs.js +++ b/src/agentscope/web/studio/static/js/dashboard-runs.js @@ -1,6 +1,6 @@ function initializeDashboardRunsPage() { //TODO: fetch runs data from server - fetch('/api/runs') + fetch('/api/runs/all') .then(response => { if (!response.ok) { throw new Error('Failed to fetch runs data'); From 14cdf3add5199d5e16673d1c698f661a06c7b003 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 22 May 2024 15:46:07 +0800 Subject: [PATCH 25/80] update client --- src/agentscope/web/client.py | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py index 71a888a52..b3e3cfa6e 100644 --- a/src/agentscope/web/client.py +++ b/src/agentscope/web/client.py @@ -82,17 +82,7 @@ def register_run( name: str, run_dir: str, ) -> bool: - """Register a run to the AgentScope Studio. - - Args: - run_id (str): _description_ - project (str): _description_ - name (str): _description_ - run_dir (str): _description_ - - Returns: - bool: _description_ - """ + """Register a run to the AgentScope Studio.""" url = f"{self.studio_url}/api/register/run" resp = requests.post( url, @@ -120,9 +110,9 @@ def send_message( url: str = None, ) -> bool: """Send a message to the studio.""" - url = f"{self.studio_url}/api/messages/put" + send_url = f"{self.studio_url}/api/messages/put" resp = requests.post( - url, + send_url, json={ "run_id": self.run_id, "name": name, From d6534151c90321453ffc59763158fbf30c300009 Mon Sep 17 00:00:00 2001 From: DavdGao Date: Wed, 22 May 2024 21:02:56 +0800 Subject: [PATCH 26/80] 1. add new font; 2. adjust speaker icon; 3. adjust runs tables; 4. add status labels in runs table; 5. implement search functionality --- src/agentscope/web/studio/static/css/base.css | 3 +- .../static/css/dashboard-detail-dialogue.css | 15 ++-- .../studio/static/css/dashboard-detail.css | 4 +- .../web/studio/static/css/dashboard-runs.css | 69 ++++++++++++++++-- src/agentscope/web/studio/static/css/font.css | 5 ++ .../web/studio/static/css/index.css | 2 +- .../static/fonts/SourceSans3-Regular.ttf | Bin 0 -> 380932 bytes .../studio/static/html/chat-row-template.html | 34 --------- .../html/dashboard-detail-dialogue.html | 2 +- .../studio/static/html/dashboard-detail.html | 2 +- .../web/studio/static/html/template.html | 47 ++++++++++++ .../static/js/dashboard-detail-dialogue.js | 25 +++++-- .../web/studio/static/js/dashboard-runs.js | 63 +++++++++++++--- 13 files changed, 199 insertions(+), 72 deletions(-) create mode 100644 src/agentscope/web/studio/static/fonts/SourceSans3-Regular.ttf delete mode 100644 src/agentscope/web/studio/static/html/chat-row-template.html create mode 100644 src/agentscope/web/studio/static/html/template.html diff --git a/src/agentscope/web/studio/static/css/base.css b/src/agentscope/web/studio/static/css/base.css index 248f3e8d0..15d431fac 100644 --- a/src/agentscope/web/studio/static/css/base.css +++ b/src/agentscope/web/studio/static/css/base.css @@ -2,8 +2,9 @@ --tab-btn-icon-length: 20px; --main-color: #59AC80; - /*--main-color-light: #7DC3A2;*/ --main-color-light: #A0D9C4; + --main-color-light-light: #D1E9DC; + --main-color-very-light: #F3F9F1; --main-color-dark: #3D7D5A; --base-color: #F3F9F1; diff --git a/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css b/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css index 977fa6fb1..7b431dfe0 100644 --- a/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css +++ b/src/agentscope/web/studio/static/css/dashboard-detail-dialogue.css @@ -86,7 +86,6 @@ width: 100%; height: 100%; margin-bottom: 50px; - padding: 5px; max-height: 100%; border: 1px solid var(--border-color); border-radius: 8px; @@ -106,11 +105,11 @@ box-sizing: border-box; width: 100%; height: fit-content; - margin: 10px 0; + padding: 10px 0; } .chat-row:hover { - background-color: var(--main-color-light); + background-color: var(--main-color-very-light); } .user.chat-row { @@ -127,12 +126,11 @@ box-sizing: border-box; width: var(--chat-icon-width); height: var(--chat-icon-height); - border-radius: 25px; + border-radius: 5px; margin: 0 var(--chat-icon-horizontal-margin); - padding: 5px; - background-color: #1c6cc2; + padding: 10px; + background-color: var(--main-color); fill: #ffffff; - } .chat-content { @@ -155,7 +153,8 @@ .chat-bubble { display: flex; flex-direction: column; - border: 1px solid var(--border-color); + border: 1px solid var(--main-color-light); + background-color: #ffffff; flex-grow: 1; box-sizing: border-box; padding: 10px; diff --git a/src/agentscope/web/studio/static/css/dashboard-detail.css b/src/agentscope/web/studio/static/css/dashboard-detail.css index 6d1950fc1..783ac084f 100644 --- a/src/agentscope/web/studio/static/css/dashboard-detail.css +++ b/src/agentscope/web/studio/static/css/dashboard-detail.css @@ -29,8 +29,8 @@ display: flex; flex-direction: column; width: 100%; - height: fit-content; - padding: 50px 10px; + height: 100%; + padding: 45px 10px; box-sizing: border-box; } diff --git a/src/agentscope/web/studio/static/css/dashboard-runs.css b/src/agentscope/web/studio/static/css/dashboard-runs.css index 70695bbf7..82fdfcc70 100644 --- a/src/agentscope/web/studio/static/css/dashboard-runs.css +++ b/src/agentscope/web/studio/static/css/dashboard-runs.css @@ -1,6 +1,6 @@ :root { - --runs-content-padding: 50px; - + --runs-content-padding-vertical: 50px; + --runs-content-padding-horizontal: 80px; --runs-content-control-panel-height: 55px; --runs-search-input-height: 35px; --runs-table-height: calc(100% - var(--runs-content-control-panel-height)); @@ -11,7 +11,7 @@ flex-direction: column; height: 100%; width: 100%; - padding: var(--runs-content-padding); + padding: var(--runs-content-padding-vertical) var(--runs-content-padding-horizontal); box-sizing: border-box; } @@ -30,8 +30,8 @@ width: 100%; height: var(--runs-table-height); max-height: var(--runs-table-height); + background-color: #ffffff; border: 1px solid var(--border-color); - background: transparent; } #runs-search-input { @@ -40,6 +40,12 @@ flex-grow: 1; align-items: center; border: 1px solid var(--border-color); + padding: 0 10px; +} + +#runs-search-input:focus { + border-color: var(--main-color); + outline: none; /* 移除默认的焦点轮廓样式 */ } /* Remove border from tabulator */ @@ -49,17 +55,64 @@ border: none !important; } +.tabulator .tabulator-cell { + height: 45px; +} + /* Remove the bottom border from table header */ .tabulator .tabulator-header { border-bottom: none !important; } -/*!* Set the same color for all rows *!*/ +.tabulator-col-sorter-element.tabulator-sortable.tabulator-col { + background-color: var(--main-color-light); + color: var(--main-color-dark); +} + +/* Set the same color for all rows */ /*.tabulator .tabulator-row {*/ /* background-color: #FFF !important; !* Replace by your color *!*/ /*}*/ -/*!* or, you just want to remove the alternative background color and keep the basic background *!*/ +/* or, you just want to remove the alternative background color and keep the basic background */ /*.tabulator .tabulator-row:nth-child(even) {*/ -/* background-color: inherit !important; !* Causes even row background colors to inherit the default or specified row background colors *!*/ -/*}*/ \ No newline at end of file +/* background-color: var(--main-color-light-light) !important; !* Causes even row background colors to inherit the default or specified row background colors *!*/ +/*}*/ + +.runs-table-status-tag { + display: flex; + flex-direction: row; + align-items: center; + height: 26px; + width: fit-content; + border-radius: 13px; + color: var(--base-color); + box-sizing: border-box; + margin: 0 5px; + padding: 0 14px; +} + +.running.runs-table-status-tag { + background-color: #d1e7fa; + color: #58c1ef; + fill: #58c1ef; +} + +.finished.runs-table-status-tag { + background-color: var(--main-color-light); + color: var(--main-color-dark); + fill: var(--main-color-dark); +} + +.unknown.runs-table-status-tag { + background-color: #e1e1e1; + color: #3d4047; + fill: #3d4047; +} + +.runs-table-status-svg { + display: flex; + width: 15px; + height: 15px; + margin-right: 7px; +} diff --git a/src/agentscope/web/studio/static/css/font.css b/src/agentscope/web/studio/static/css/font.css index ad0bb6d78..5b151527f 100644 --- a/src/agentscope/web/studio/static/css/font.css +++ b/src/agentscope/web/studio/static/css/font.css @@ -1,4 +1,9 @@ @font-face { font-family: 'krypton'; src: url('/static/fonts/KRYPTON.ttf') format('truetype'); +} + +@font-face { + font-family: 'source-sans'; + src: url('/static/fonts/SourceSans3-Regular.ttf') format('truetype'); } \ No newline at end of file diff --git a/src/agentscope/web/studio/static/css/index.css b/src/agentscope/web/studio/static/css/index.css index 5f0f780d8..e4c617759 100644 --- a/src/agentscope/web/studio/static/css/index.css +++ b/src/agentscope/web/studio/static/css/index.css @@ -29,7 +29,7 @@ body { flex-direction: row; padding: 0; background: var(--body-bg); - font-family: sans-serif; + font-family: source-sans, sans-serif; box-sizing: border-box; } diff --git a/src/agentscope/web/studio/static/fonts/SourceSans3-Regular.ttf b/src/agentscope/web/studio/static/fonts/SourceSans3-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c906bdab86660cc43369aae4825697f539c91ddc GIT binary patch literal 380932 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 wfhR6_fpK6QdLtp!IL)Dn(9 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≻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 BCPe@M literal 0 HcmV?d00001 diff --git a/src/agentscope/web/studio/static/html/chat-row-template.html b/src/agentscope/web/studio/static/html/chat-row-template.html deleted file mode 100644 index c420717b9..000000000 --- a/src/agentscope/web/studio/static/html/chat-row-template.html +++ /dev/null @@ -1,34 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/agentscope/web/studio/static/html/dashboard-detail-dialogue.html b/src/agentscope/web/studio/static/html/dashboard-detail-dialogue.html index 24a72eefb..5147c43f4 100644 --- a/src/agentscope/web/studio/static/html/dashboard-detail-dialogue.html +++ b/src/agentscope/web/studio/static/html/dashboard-detail-dialogue.html @@ -7,7 +7,7 @@

+ placeholder="Input message here ...">
User diff --git a/src/agentscope/web/studio/static/html/dashboard-detail.html b/src/agentscope/web/studio/static/html/dashboard-detail.html index 7e9df72e8..29429efc1 100644 --- a/src/agentscope/web/studio/static/html/dashboard-detail.html +++ b/src/agentscope/web/studio/static/html/dashboard-detail.html @@ -27,7 +27,7 @@ xmlns="http://www.w3.org/2000/svg"> - Model Invocation + API Invocation
diff --git a/src/agentscope/web/studio/static/html/template.html b/src/agentscope/web/studio/static/html/template.html new file mode 100644 index 000000000..5f85b1394 --- /dev/null +++ b/src/agentscope/web/studio/static/html/template.html @@ -0,0 +1,47 @@ + + + + + + + \ No newline at end of file diff --git a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js index f0cbf8295..4bf8eb661 100644 --- a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js @@ -1,9 +1,20 @@ //TODO: move the following to chat js -let chatRowOtherTemplate, chatRowUserTemplate, infoRowTemplate; +let chatRowOtherTemplate, chatRowUserTemplate, chatRowSystemTemplate, infoRowTemplate; + +let agentIcons = [ + '', + '', + '', + '', + '', + '', +] + +let name2Color = {} async function loadChatTemplate() { - const response = await fetch('static/html/chat-row-template.html'); + const response = await fetch('static/html/template.html'); const htmlText = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(htmlText, 'text/html'); @@ -11,6 +22,7 @@ async function loadChatTemplate() { // save chatRowOtherTemplate = doc.querySelector('#chat-row-other-template').content; chatRowUserTemplate = doc.querySelector('#chat-row-user-template').content; + chatRowSystemTemplate = doc.querySelector('#chat-row-system-template').content; infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; } @@ -77,7 +89,6 @@ function _renderMultiModalData(url) { function _addUserChatRow(index, pMsg) { const template = chatRowUserTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). template.querySelector('.chat-name').textContent = pMsg.name; let chatBubble = template.querySelector('.chat-bubble'); chatBubble.textContent = pMsg.content; @@ -88,7 +99,8 @@ function _addUserChatRow(index, pMsg) { function _addAssistantChatRow(index, pMsg) { const template = chatRowOtherTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). + // TODO: 替换头像 + template.querySelector('.chat-name').textContent = pMsg.name; let chatBubble = template.querySelector('.chat-bubble'); chatBubble.textContent = pMsg.content; @@ -98,8 +110,7 @@ function _addAssistantChatRow(index, pMsg) { } function _addSystemChatRow(index, pMsg) { - const template = chatRowOtherTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). + const template = chatRowSystemTemplate.cloneNode(true); template.querySelector('.chat-name').textContent = pMsg.name; let chatBubble = template.querySelector('.chat-bubble'); chatBubble.textContent = pMsg.content; @@ -129,7 +140,7 @@ async function initializeDashboardDetailDialoguePage(pRuntimeInfo) { await loadChatTemplate(); // Fetch the chat history from backend - fetch("/api/messages/" + pRuntimeInfo.id) + fetch("/api/messages/run/" + pRuntimeInfo.id) .then(response => { if (!response.ok) { throw new Error('Failed to fetch messages data'); diff --git a/src/agentscope/web/studio/static/js/dashboard-runs.js b/src/agentscope/web/studio/static/js/dashboard-runs.js index 9953f6fb2..f7880ca94 100644 --- a/src/agentscope/web/studio/static/js/dashboard-runs.js +++ b/src/agentscope/web/studio/static/js/dashboard-runs.js @@ -1,6 +1,40 @@ +// Search functionality for runs table +function allColumnFilter(data) { + let searchValue = document.getElementById('runs-search-input').value.toLowerCase(); + + for (var field in data) { + if (data.hasOwnProperty(field)) { + var value = data[field]; + + if (value != null) { + value = value.toString().toLowerCase(); + if (value.includes(searchValue)) { + return true; + } + } + } + } + return false; +} + +function renderIconInRunsTable(cell, formatterParams, onRendered){ + let value = cell.getValue(); + console.log(value) + switch (value) { + case 'running': + return '
running
' + + case 'finished': + return '
finished
' + + default: + return '
unknown
' + } +} + function initializeDashboardRunsPage() { //TODO: fetch runs data from server - fetch('/api/runs') + fetch('/api/runs/all') .then(response => { if (!response.ok) { throw new Error('Failed to fetch runs data'); @@ -10,17 +44,28 @@ function initializeDashboardRunsPage() { .then(data => { var runsTable = new Tabulator("#runs-table", { data: data, - columns: [ - {title: "Status", field: "status", editor: false}, - {title: "ID", field: "id", editor: false}, - {title: "Project", field: "project", editor: false}, - {title: "Name", field: "name", editor: false}, - {title: "Create time", field: "create_time", editor: false}, + {title: "Status", field: "status", editor: false, vertAlign:"middle", formatter: renderIconInRunsTable}, + {title: "ID", field: "id", editor: false, vertAlign:"middle"}, + {title: "Project", field: "project", editor: false, vertAlign:"middle"}, + {title: "Name", field: "name", editor: false, vertAlign:"middle"}, + {title: "Create time", field: "create_time", editor: false, vertAlign:"middle"}, ], - layout: "fitDataStretch", + layout: "fitColumns", }) + // Search logic + document.getElementById('runs-search-input').addEventListener('input', function(e) { + let searchValue = e.target.value; + if (searchValue) { + // Filter the table + runsTable.setFilter(allColumnFilter); + } else { + //Clear the filter + runsTable.clearFilter(); + } + }); + // Set up row click event runsTable.on("rowClick", function (e, row) { // Jump to the run detail page @@ -28,6 +73,6 @@ function initializeDashboardRunsPage() { }); }) .catch(error => { - + console.error(error); }); } \ No newline at end of file From f92105a661834fe6207ff973f7c6aeb042f2e19c Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Wed, 22 May 2024 21:07:35 +0800 Subject: [PATCH 27/80] studio dialogue finish --- src/agentscope/agents/user_agent.py | 2 +- src/agentscope/web/client.py | 8 +- src/agentscope/web/studio/_app.py | 40 ++- .../static/js/dashboard-detail-dialogue.js | 315 ++++++++++-------- .../web/studio/templates/index.html | 1 + 5 files changed, 225 insertions(+), 141 deletions(-) diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index 71c403ee0..8956fab7f 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -82,7 +82,7 @@ def reply( ) raw_input = self.input_client.get_user_input() content = raw_input["content"] - url = None + url = raw_input.get("url", None) kwargs = {} else: time.sleep(0.5) diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py index b3e3cfa6e..36e00c718 100644 --- a/src/agentscope/web/client.py +++ b/src/agentscope/web/client.py @@ -51,7 +51,13 @@ def get_user_input(self) -> Optional[dict]: input is received. """ self.input_event.clear() - self.sio.emit("request_user_input") + self.sio.emit( + "request_user_input", + { + "run_id": self.run_id, + "agent_id": self.agent_id, + }, + ) self.input_event.wait() return self.user_input diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index c081b8d2f..8d8e2a475 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -38,6 +38,7 @@ class Run(db.Model): # type: ignore[name-defined] script_path = db.Column(db.String) run_dir = db.Column(db.String) create_time = db.Column(db.DateTime, default=datetime.now) + status = db.Column(db.String, default="finished") class Server(db.Model): # type: ignore[name-defined] @@ -88,6 +89,7 @@ def get_runs() -> list: "script_path": run.script_path, "run_dir": run.run_dir, "create_time": run.create_time.isoformat(), + "status": run.status, } for run in runs ] @@ -115,6 +117,7 @@ def register_run() -> Response: project=project, name=name, run_dir=run_dir, + status="running", ), ) db.session.commit() @@ -180,8 +183,11 @@ def put_message() -> Response: { "run_id": run_id, "name": name, + "role": role, "content": content, "url": url, + "metadata": metadata, + "timestamp": timestamp, }, room=run_id, ) @@ -299,12 +305,33 @@ def run_detail(run_dir: str) -> str: return render_template("run.html", runInfo=logging_and_dialog) -@socketio.on("user_input") -def user_input(data: dict) -> None: +@socketio.on("request_user_input") +def request_user_input(data: dict) -> None: + """Request user input""" + print("request user input") + run_id = data["run_id"] + agent_id = data["agent_id"] + db.session.query(Run).filter_by(id=run_id).update({"status": "waiting"}) + db.session.commit() + socketio.emit( + "enable_user_input", + { + "run_id": run_id, + "agent_id": agent_id, + }, + room=run_id, + ) + + +@socketio.on("user_input_ready") +def user_input_ready(data: dict) -> None: """Get user input and send to the agent""" + print("user input ready") run_id = data["run_id"] content = data["content"] url = data.get("url", None) + db.session.query(Run).filter_by(id=run_id).update({"status": "running"}) + db.session.commit() socketio.emit( "fetch_user_input", { @@ -333,6 +360,15 @@ def on_join(data: dict) -> None: """Join a websocket room""" run_id = data["run_id"] join_room(run_id) + run = Run.query.filter_by(id=run_id).first() + if run and run.status == "waiting": + socketio.emit( + "enable_user_input", + { + "run_id": run_id, + }, + room=run_id, + ) @socketio.on("leave") diff --git a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js index 1c4f4725c..1f52e5121 100644 --- a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js @@ -3,169 +3,210 @@ let chatRowOtherTemplate, chatRowUserTemplate, infoRowTemplate; async function loadChatTemplate() { - const response = await fetch('static/html/chat-row-template.html'); - const htmlText = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlText, 'text/html'); - - // save - chatRowOtherTemplate = doc.querySelector('#chat-row-other-template').content; - chatRowUserTemplate = doc.querySelector('#chat-row-user-template').content; - infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; + const response = await fetch("static/html/chat-row-template.html"); + const htmlText = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlText, "text/html"); + + // save + chatRowOtherTemplate = doc.querySelector("#chat-row-other-template").content; + chatRowUserTemplate = doc.querySelector("#chat-row-user-template").content; + infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; } // Add a chat row according to the role field in the message function addChatRow(index, pMsg) { - switch (pMsg.role.toLowerCase()) { - case "user": - return _addUserChatRow(index, pMsg) - break; - case "assistant": - return _addAssistantChatRow(index, pMsg) - break; - case "system": - return _addSystemChatRow(index, pMsg) - break; - } + switch (pMsg.role.toLowerCase()) { + case "user": + return _addUserChatRow(index, pMsg); + case "assistant": + return _addAssistantChatRow(index, pMsg); + case "system": + return _addSystemChatRow(index, pMsg); + } } function _determineFileType(url) { - // Image - let img_suffix = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.svg', '.webp']; - // Video - let video_suffix = ['.mp4', '.webm', '.avi', '.mov', '.wmv', '.flv', '.f4v', '.m4v', '.rmvb', '.rm', '.3gp', '.dat', '.ts', '.mts', '.vob']; - // Audio - let audio_suffix = ['.mp3', '.wav', '.wma', '.ogg', '.aac', '.flac']; - - if (img_suffix.some(suffix => url.endsWith(suffix))) { - return 'image'; - } else if (video_suffix.some(suffix => url.endsWith(suffix))) { - return 'video'; - } else if (audio_suffix.some(suffix => url.endsWith(suffix))) { - return 'audio'; - } - return 'file'; + // Image + let img_suffix = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"]; + // Video + let video_suffix = [ + ".mp4", + ".webm", + ".avi", + ".mov", + ".wmv", + ".flv", + ".f4v", + ".m4v", + ".rmvb", + ".rm", + ".3gp", + ".dat", + ".ts", + ".mts", + ".vob", + ]; + // Audio + let audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; + + if (img_suffix.some((suffix) => url.endsWith(suffix))) { + return "image"; + } else if (video_suffix.some((suffix) => url.endsWith(suffix))) { + return "video"; + } else if (audio_suffix.some((suffix) => url.endsWith(suffix))) { + return "audio"; + } + return "file"; } function _renderMultiModalData(url) { - if (!url || url === '') { - return ''; - } - // Determine the type of the file - let urlType = _determineFileType(url); - - // If we need to fetch the url from the backend - let src = null; - if (url.startsWith("http://") || url.startsWith("https://")) { - // Obtain the url from the backend - src = url; - } else { - src = '/file?url=' + url; - } - - switch (urlType) { - case 'image': - return `Image`; - case 'audio': - return ``; - case 'video': - return ``; - default: - return `
${url}`; - } + if (!url || url === "") { + return ""; + } + // Determine the type of the file + let urlType = _determineFileType(url); + + // If we need to fetch the url from the backend + let src = null; + if (url.startsWith("http://") || url.startsWith("https://")) { + // Obtain the url from the backend + src = url; + } else { + src = "/file?url=" + url; + } + + switch (urlType) { + case "image": + return `Image`; + case "audio": + return ``; + case "video": + return ``; + default: + return `${url}`; + } } function _addUserChatRow(index, pMsg) { - const template = chatRowUserTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). - template.querySelector('.chat-name').textContent = pMsg.name; - let chatBubble = template.querySelector('.chat-bubble'); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector('.chat-row').setAttribute('data-index', index); - return template.firstElementChild.outerHTML; + const template = chatRowUserTemplate.cloneNode(true); + // template.querySelector('.chat-icon'). + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addAssistantChatRow(index, pMsg) { - const template = chatRowOtherTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). - template.querySelector('.chat-name').textContent = pMsg.name; - let chatBubble = template.querySelector('.chat-bubble'); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector('.chat-row').setAttribute('data-index', index); - return template.firstElementChild.outerHTML; + const template = chatRowOtherTemplate.cloneNode(true); + // template.querySelector('.chat-icon'). + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addSystemChatRow(index, pMsg) { - const template = chatRowOtherTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). - template.querySelector('.chat-name').textContent = pMsg.name; - let chatBubble = template.querySelector('.chat-bubble'); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector('.chat-row').setAttribute('data-index', index); - return template.firstElementChild.outerHTML; + const template = chatRowOtherTemplate.cloneNode(true); + // template.querySelector('.chat-icon'). + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addKeyValueInfoRow(pKey, pValue) { - const template = infoRowTemplate.cloneNode(true); - template.querySelector('.dialogue-info-key').textContent = pKey.toUpperCase(); - template.querySelector('.dialogue-info-value').textContent = pValue; - return template.firstElementChild.outerHTML; + const template = infoRowTemplate.cloneNode(true); + template.querySelector(".dialogue-info-key").textContent = pKey.toUpperCase(); + template.querySelector(".dialogue-info-value").textContent = pValue; + return template.firstElementChild.outerHTML; } function _showInfoInDialogueDetailContent(data) { - let infoRows = Object.keys(data).map(key => _addKeyValueInfoRow(key, data[key])); - let infoClusterize = new Clusterize({ - rows: infoRows, - scrollId: 'chat-detail', - contentId: 'dialogue-detail-content' - }); + let infoRows = Object.keys(data).map((key) => + _addKeyValueInfoRow(key, data[key]) + ); + let infoClusterize = new Clusterize({ + rows: infoRows, + scrollId: "chat-detail", + contentId: "dialogue-detail-content", + }); } async function initializeDashboardDetailDialoguePage(pRuntimeInfo) { - // Load the chat template - await loadChatTemplate(); - - // Fetch the chat history from backend - fetch("/api/messages/run/" + pRuntimeInfo.id) - .then(response => { - if (!response.ok) { - throw new Error('Failed to fetch messages data'); - } - return response.json(); - }) - .then(data => { - // Load the chat history - let chatRows = data.map((msg, index) => addChatRow(index, msg)) - var clusterize = new Clusterize({ - rows: chatRows, - scrollId: 'chat-box', - contentId: 'chat-box-content' - }); - - document.getElementById('chat-box-content') - addEventListener('click', function (event) { - let target = event.target; - - // 检查是否点击了行元素,并获取 `data-index` - while (target && target !== this) { - if (target.matches('.chat-row')) { - let rowIndex = target.getAttribute('data-index'); - console.log("The " + rowIndex + " message is clicked."); - _showInfoInDialogueDetailContent(data[rowIndex]) - break; - } - target = target.parentNode; - } - }); - - // Load the detail content in the right panel - // traverse all the keys in pRuntimeInfo and create a key-value row - _showInfoInDialogueDetailContent(pRuntimeInfo) - }) - .catch(error => { - console.error('Failed to fetch messages data:', error); - }) + // Load the chat template + await loadChatTemplate(); + + // Fetch the chat history from backend + fetch("/api/messages/run/" + pRuntimeInfo.id) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch messages data"); + } + return response.json(); + }) + .then((data) => { + let send_btn = document.getElementById("chat-input-send-btn"); + send_btn.disabled = true; + // Load the chat history + let chatRows = data.map((msg, index) => addChatRow(index, msg)); + var clusterize = new Clusterize({ + rows: chatRows, + scrollId: "chat-box", + contentId: "chat-box-content", + }); + + document.getElementById("chat-box-content"); + addEventListener("click", function (event) { + let target = event.target; + + while (target && target !== this) { + if (target.matches(".chat-row")) { + let rowIndex = target.getAttribute("data-index"); + console.log("The " + rowIndex + " message is clicked."); + _showInfoInDialogueDetailContent(data[rowIndex]); + break; + } + target = target.parentNode; + } + }); + // Load the detail content in the right panel + // traverse all the keys in pRuntimeInfo and create a key-value row + _showInfoInDialogueDetailContent(pRuntimeInfo); + + var socket = io(); + socket.on("connect", () => { + socket.emit("join", { run_id: pRuntimeInfo.id }); + send_btn.onclick = () => { + var message = document.getElementById("chat-input-textarea").value; + socket.emit("user_input_ready", { + content: message, + run_id: pRuntimeInfo.id, + }); + document.getElementById("chat-input-textarea").value = ""; + document.getElementById("chat-input-send-btn").disabled = true; + }; + }); + socket.on("display_message", (data) => { + if (data.run_id == pRuntimeInfo.id) { + let row = addChatRow(clusterize.getRowsAmount(), data); + clusterize.append([row]); + clusterize.refresh(); + } + }); + socket.on("enable_user_input", (data) => { + if (data.run_id == pRuntimeInfo.id) { + send_btn.disabled = false; + } + }); + }) + .catch((error) => { + console.error("Failed to fetch messages data:", error); + }); } diff --git a/src/agentscope/web/studio/templates/index.html b/src/agentscope/web/studio/templates/index.html index 5bc2ca670..384bf6c84 100644 --- a/src/agentscope/web/studio/templates/index.html +++ b/src/agentscope/web/studio/templates/index.html @@ -17,6 +17,7 @@ href="{{ url_for('static', filename='css/tabulator.min.css') }}"> + Date: Thu, 23 May 2024 11:33:21 +0800 Subject: [PATCH 28/80] finish dashboard dialogue page --- src/agentscope/agents/user_agent.py | 1 + src/agentscope/web/client.py | 35 +- src/agentscope/web/gradio/app.py | 28 -- src/agentscope/web/studio/_app.py | 15 +- .../web/studio/static/css/dashboard-runs.css | 6 + .../web/studio/static/html/dashboard.html | 4 +- .../static/js/dashboard-detail-dialogue.js | 378 +++++++++--------- .../web/studio/static/js/dashboard-runs.js | 99 +++-- .../web/studio/static/js/dashboard.js | 58 +-- src/agentscope/web/studio/static/js/index.js | 119 +++--- 10 files changed, 407 insertions(+), 336 deletions(-) delete mode 100644 src/agentscope/web/gradio/app.py diff --git a/src/agentscope/agents/user_agent.py b/src/agentscope/agents/user_agent.py index 8956fab7f..3c7d39e20 100644 --- a/src/agentscope/agents/user_agent.py +++ b/src/agentscope/agents/user_agent.py @@ -33,6 +33,7 @@ def __init__(self, name: str = "User", require_url: bool = False) -> None: if _runtime.studio_client is not None: self.input_client = ( _runtime.studio_client.generate_user_input_client( + self.name, self.agent_id, ) ) diff --git a/src/agentscope/web/client.py b/src/agentscope/web/client.py index 36e00c718..4dac0bb0f 100644 --- a/src/agentscope/web/client.py +++ b/src/agentscope/web/client.py @@ -2,6 +2,7 @@ """The client for agentscope platform.""" from threading import Event from typing import Optional +import atexit import requests import socketio @@ -16,10 +17,12 @@ def __init__( self, studio_url: str, run_id: str, + name: str, agent_id: str, ) -> None: self.studio_url = studio_url self.run_id = run_id + self.name = name self.agent_id = agent_id self.user_input = None self.sio = socketio.Client() @@ -55,6 +58,7 @@ def get_user_input(self) -> Optional[dict]: "request_user_input", { "run_id": self.run_id, + "name": self.name, "agent_id": self.agent_id, }, ) @@ -78,9 +82,18 @@ def __init__( self.studio_url = studio_url self.run_id = run_id - def generate_user_input_client(self, agent_id: str) -> WebSocketClient: + def generate_user_input_client( + self, + name: str, + agent_id: str, + ) -> WebSocketClient: """Generate a websocket client for a specifc user agent.""" - return WebSocketClient(self.studio_url, self.run_id, agent_id=agent_id) + return WebSocketClient( + self.studio_url, + self.run_id, + name=name, + agent_id=agent_id, + ) def register_run( self, @@ -100,11 +113,19 @@ def register_run( }, timeout=10, # todo: configurable timeout ) - if resp.status_code == 200: - return True - else: - logger.warning(f"Fail to register to studio: {resp}") - raise RuntimeError(f"Fail to register to studio: {resp}") + if resp.status_code != 200: + logger.warning(f"Fail to register to studio: {resp.text}") + raise RuntimeError(f"Fail to register to studio: {resp.text}") + + def finish_run() -> None: + requests.post( + f"{self.studio_url}/api/runs/finish", + json={"run_id": self.run_id}, + timeout=5, + ) + + atexit.register(finish_run) + return True def send_message( self, diff --git a/src/agentscope/web/gradio/app.py b/src/agentscope/web/gradio/app.py deleted file mode 100644 index b074696b4..000000000 --- a/src/agentscope/web/gradio/app.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -"""run agentscope studio server""" -import argparse -import os -import sys -import threading -import time -from collections import defaultdict -from typing import Optional, Callable -import traceback - -from flask import Flask, request, jsonify, render_template, Response -from flask_cors import CORS -from flask_socketio import SocketIO - -app = Flask(__name__) -CORS(app) -socketio = SocketIO(app) - - -@app.route("/", methods=["GET"]) -def index() -> Response: - """_summary_ - - Returns: - Response: _description_ - """ - return render_template("index.html") diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index 8d8e2a475..8e344a59f 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -109,8 +109,8 @@ def register_run() -> Response: run_dir = data.get("run_dir") # check if the run_id is already in the database if Run.query.filter_by(id=run_id).first(): - print(f"Run id {run_id} already exists.") - abort(400, f"RUN_ID {run_id} already exists") + print(f"run id {run_id} already exists.") + abort(400, f"run_id [{run_id}] already exists") db.session.add( Run( id=run_id, @@ -212,6 +212,15 @@ def get_available_run_id() -> Response: return jsonify({"run_id": uuid.uuid4().hex}) +@app.route("/api/runs/finish", methods=["POST"]) +def finish_run() -> Response: + """Finish a run.""" + run_id = request.json["run_id"] + db.session.query(Run).filter_by(id=run_id).update({"status": "finished"}) + db.session.commit() + return jsonify(status="ok") + + @app.route("/file") def get_local_file() -> Response: """Get the local file via the url.""" @@ -310,6 +319,7 @@ def request_user_input(data: dict) -> None: """Request user input""" print("request user input") run_id = data["run_id"] + name = data["name"] agent_id = data["agent_id"] db.session.query(Run).filter_by(id=run_id).update({"status": "waiting"}) db.session.commit() @@ -317,6 +327,7 @@ def request_user_input(data: dict) -> None: "enable_user_input", { "run_id": run_id, + "name": name, "agent_id": agent_id, }, room=run_id, diff --git a/src/agentscope/web/studio/static/css/dashboard-runs.css b/src/agentscope/web/studio/static/css/dashboard-runs.css index 82fdfcc70..73e7712dd 100644 --- a/src/agentscope/web/studio/static/css/dashboard-runs.css +++ b/src/agentscope/web/studio/static/css/dashboard-runs.css @@ -104,6 +104,12 @@ fill: var(--main-color-dark); } +.waiting.runs-table-status-tag { + background-color: #f8efba; + color: #f6b93b; + fill: #f6b93b; +} + .unknown.runs-table-status-tag { background-color: #e1e1e1; color: #3d4047; diff --git a/src/agentscope/web/studio/static/html/dashboard.html b/src/agentscope/web/studio/static/html/dashboard.html index 3deac9d2e..8003b1f90 100644 --- a/src/agentscope/web/studio/static/html/dashboard.html +++ b/src/agentscope/web/studio/static/html/dashboard.html @@ -2,7 +2,5 @@
Dashboard
-
-
+
- diff --git a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js index 003f39fcb..82d8741b4 100644 --- a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js @@ -1,6 +1,9 @@ //TODO: move the following to chat js -let chatRowOtherTemplate, chatRowUserTemplate, chatRowSystemTemplate, infoRowTemplate; +let chatRowOtherTemplate, + chatRowUserTemplate, + chatRowSystemTemplate, + infoRowTemplate; let agentIcons = [ '', @@ -9,216 +12,227 @@ let agentIcons = [ '', '', '', -] +]; -let name2Color = {} +let name2Color = {}; async function loadChatTemplate() { - const response = await fetch('static/html/template.html'); - const htmlText = await response.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlText, "text/html"); - - // save - chatRowOtherTemplate = doc.querySelector('#chat-row-other-template').content; - chatRowUserTemplate = doc.querySelector('#chat-row-user-template').content; - chatRowSystemTemplate = doc.querySelector('#chat-row-system-template').content; - infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; + const response = await fetch("static/html/template.html"); + const htmlText = await response.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlText, "text/html"); + + // save + chatRowOtherTemplate = doc.querySelector( + "#chat-row-other-template" + ).content; + chatRowUserTemplate = doc.querySelector("#chat-row-user-template").content; + chatRowSystemTemplate = doc.querySelector( + "#chat-row-system-template" + ).content; + infoRowTemplate = doc.querySelector("#dialogue-info-row-template").content; } // Add a chat row according to the role field in the message function addChatRow(index, pMsg) { - switch (pMsg.role.toLowerCase()) { - case "user": - return _addUserChatRow(index, pMsg); - case "assistant": - return _addAssistantChatRow(index, pMsg); - case "system": - return _addSystemChatRow(index, pMsg); - } + switch (pMsg.role.toLowerCase()) { + case "user": + return _addUserChatRow(index, pMsg); + case "assistant": + return _addAssistantChatRow(index, pMsg); + case "system": + return _addSystemChatRow(index, pMsg); + } } function _determineFileType(url) { - // Image - let img_suffix = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"]; - // Video - let video_suffix = [ - ".mp4", - ".webm", - ".avi", - ".mov", - ".wmv", - ".flv", - ".f4v", - ".m4v", - ".rmvb", - ".rm", - ".3gp", - ".dat", - ".ts", - ".mts", - ".vob", - ]; - // Audio - let audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; - - if (img_suffix.some((suffix) => url.endsWith(suffix))) { - return "image"; - } else if (video_suffix.some((suffix) => url.endsWith(suffix))) { - return "video"; - } else if (audio_suffix.some((suffix) => url.endsWith(suffix))) { - return "audio"; - } - return "file"; + // Image + let img_suffix = [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".svg", ".webp"]; + // Video + let video_suffix = [ + ".mp4", + ".webm", + ".avi", + ".mov", + ".wmv", + ".flv", + ".f4v", + ".m4v", + ".rmvb", + ".rm", + ".3gp", + ".dat", + ".ts", + ".mts", + ".vob", + ]; + // Audio + let audio_suffix = [".mp3", ".wav", ".wma", ".ogg", ".aac", ".flac"]; + + if (img_suffix.some((suffix) => url.endsWith(suffix))) { + return "image"; + } else if (video_suffix.some((suffix) => url.endsWith(suffix))) { + return "video"; + } else if (audio_suffix.some((suffix) => url.endsWith(suffix))) { + return "audio"; + } + return "file"; } function _renderMultiModalData(url) { - if (!url || url === "") { - return ""; - } - // Determine the type of the file - let urlType = _determineFileType(url); - - // If we need to fetch the url from the backend - let src = null; - if (url.startsWith("http://") || url.startsWith("https://")) { - // Obtain the url from the backend - src = url; - } else { - src = "/file?url=" + url; - } - - switch (urlType) { - case "image": - return `Image`; - case "audio": - return ``; - case "video": - return ``; - default: - return `${url}`; - } + if (!url || url === "") { + return ""; + } + // Determine the type of the file + let urlType = _determineFileType(url); + + // If we need to fetch the url from the backend + let src = null; + if (url.startsWith("http://") || url.startsWith("https://")) { + // Obtain the url from the backend + src = url; + } else { + src = "/file?url=" + url; + } + + switch (urlType) { + case "image": + return `Image`; + case "audio": + return ``; + case "video": + return ``; + default: + return `${url}`; + } } function _addUserChatRow(index, pMsg) { - const template = chatRowUserTemplate.cloneNode(true); - // template.querySelector('.chat-icon'). - template.querySelector(".chat-name").textContent = pMsg.name; - let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector(".chat-row").setAttribute("data-index", index); - return template.firstElementChild.outerHTML; + const template = chatRowUserTemplate.cloneNode(true); + // template.querySelector('.chat-icon'). + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addAssistantChatRow(index, pMsg) { - const template = chatRowOtherTemplate.cloneNode(true); - // TODO: avator - - template.querySelector('.chat-name').textContent = pMsg.name; - let chatBubble = template.querySelector('.chat-bubble'); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector('.chat-row').setAttribute('data-index', index); - return template.firstElementChild.outerHTML; + const template = chatRowOtherTemplate.cloneNode(true); + // TODO: avator + + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addSystemChatRow(index, pMsg) { - const template = chatRowSystemTemplate.cloneNode(true); - template.querySelector('.chat-name').textContent = pMsg.name; - let chatBubble = template.querySelector('.chat-bubble'); - chatBubble.textContent = pMsg.content; - chatBubble.innerHTML += _renderMultiModalData(pMsg.url); - template.querySelector('.chat-row').setAttribute('data-index', index); - return template.firstElementChild.outerHTML; + const template = chatRowSystemTemplate.cloneNode(true); + template.querySelector(".chat-name").textContent = pMsg.name; + let chatBubble = template.querySelector(".chat-bubble"); + chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += _renderMultiModalData(pMsg.url); + template.querySelector(".chat-row").setAttribute("data-index", index); + return template.firstElementChild.outerHTML; } function _addKeyValueInfoRow(pKey, pValue) { - const template = infoRowTemplate.cloneNode(true); - template.querySelector(".dialogue-info-key").textContent = pKey.toUpperCase(); - template.querySelector(".dialogue-info-value").textContent = pValue; - return template.firstElementChild.outerHTML; + const template = infoRowTemplate.cloneNode(true); + template.querySelector(".dialogue-info-key").textContent = + pKey.toUpperCase(); + template.querySelector(".dialogue-info-value").textContent = pValue; + return template.firstElementChild.outerHTML; } function _showInfoInDialogueDetailContent(data) { - let infoRows = Object.keys(data).map((key) => - _addKeyValueInfoRow(key, data[key]) - ); - let infoClusterize = new Clusterize({ - rows: infoRows, - scrollId: "chat-detail", - contentId: "dialogue-detail-content", - }); + let infoRows = Object.keys(data).map((key) => + _addKeyValueInfoRow(key, data[key]) + ); + let infoClusterize = new Clusterize({ + rows: infoRows, + scrollId: "chat-detail", + contentId: "dialogue-detail-content", + }); } async function initializeDashboardDetailDialoguePage(pRuntimeInfo) { - // Load the chat template - await loadChatTemplate(); - - // Fetch the chat history from backend - fetch("/api/messages/run/" + pRuntimeInfo.id) - .then((response) => { - if (!response.ok) { - throw new Error("Failed to fetch messages data"); - } - return response.json(); - }) - .then((data) => { - let send_btn = document.getElementById("chat-input-send-btn"); - send_btn.disabled = true; - // Load the chat history - let chatRows = data.map((msg, index) => addChatRow(index, msg)); - var clusterize = new Clusterize({ - rows: chatRows, - scrollId: "chat-box", - contentId: "chat-box-content", - }); - - document.getElementById("chat-box-content"); - addEventListener("click", function (event) { - let target = event.target; - - while (target && target !== this) { - if (target.matches(".chat-row")) { - let rowIndex = target.getAttribute("data-index"); - console.log("The " + rowIndex + " message is clicked."); - _showInfoInDialogueDetailContent(data[rowIndex]); - break; - } - target = target.parentNode; - } - }); - // Load the detail content in the right panel - // traverse all the keys in pRuntimeInfo and create a key-value row - _showInfoInDialogueDetailContent(pRuntimeInfo); - - var socket = io(); - socket.on("connect", () => { - socket.emit("join", { run_id: pRuntimeInfo.id }); - send_btn.onclick = () => { - var message = document.getElementById("chat-input-textarea").value; - socket.emit("user_input_ready", { - content: message, - run_id: pRuntimeInfo.id, - }); - document.getElementById("chat-input-textarea").value = ""; - document.getElementById("chat-input-send-btn").disabled = true; - }; - }); - socket.on("display_message", (data) => { - if (data.run_id == pRuntimeInfo.id) { - let row = addChatRow(clusterize.getRowsAmount(), data); - clusterize.append([row]); - clusterize.refresh(); - } - }); - socket.on("enable_user_input", (data) => { - if (data.run_id == pRuntimeInfo.id) { - send_btn.disabled = false; - } - }); - }) - .catch((error) => { - console.error("Failed to fetch messages data:", error); - }); + // Load the chat template + await loadChatTemplate(); + + // Fetch the chat history from backend + fetch("/api/messages/run/" + pRuntimeInfo.id) + .then((response) => { + if (!response.ok) { + throw new Error("Failed to fetch messages data"); + } + return response.json(); + }) + .then((data) => { + let send_btn = document.getElementById("chat-input-send-btn"); + send_btn.disabled = true; + // Load the chat history + let chatRows = data.map((msg, index) => addChatRow(index, msg)); + var clusterize = new Clusterize({ + rows: chatRows, + scrollId: "chat-box", + contentId: "chat-box-content", + }); + + document.getElementById("chat-box-content"); + addEventListener("click", function (event) { + let target = event.target; + + while (target && target !== this) { + if (target.matches(".chat-row")) { + let rowIndex = target.getAttribute("data-index"); + console.log("The " + rowIndex + " message is clicked."); + _showInfoInDialogueDetailContent(data[rowIndex]); + break; + } + target = target.parentNode; + } + }); + // Load the detail content in the right panel + // traverse all the keys in pRuntimeInfo and create a key-value row + _showInfoInDialogueDetailContent(pRuntimeInfo); + + var socket = io(); + socket.on("connect", () => { + socket.emit("join", { run_id: pRuntimeInfo.id }); + send_btn.onclick = () => { + var message = document.getElementById( + "chat-input-textarea" + ).value; + socket.emit("user_input_ready", { + content: message, + run_id: pRuntimeInfo.id, + }); + document.getElementById("chat-input-textarea").value = ""; + document.getElementById( + "chat-input-send-btn" + ).disabled = true; + }; + }); + socket.on("display_message", (data) => { + if (data.run_id == pRuntimeInfo.id) { + let row = addChatRow(clusterize.getRowsAmount(), data); + clusterize.append([row]); + clusterize.refresh(); + } + }); + socket.on("enable_user_input", (data) => { + if (data.run_id == pRuntimeInfo.id) { + send_btn.disabled = false; + document.getElementById("chat-input-name").textContent = + data.name; + } + }); + }) + .catch((error) => { + console.error("Failed to fetch messages data:", error); + }); } diff --git a/src/agentscope/web/studio/static/js/dashboard-runs.js b/src/agentscope/web/studio/static/js/dashboard-runs.js index f7880ca94..6ebe462a7 100644 --- a/src/agentscope/web/studio/static/js/dashboard-runs.js +++ b/src/agentscope/web/studio/static/js/dashboard-runs.js @@ -1,6 +1,8 @@ // Search functionality for runs table function allColumnFilter(data) { - let searchValue = document.getElementById('runs-search-input').value.toLowerCase(); + let searchValue = document + .getElementById("runs-search-input") + .value.toLowerCase(); for (var field in data) { if (data.hasOwnProperty(field)) { @@ -17,62 +19,97 @@ function allColumnFilter(data) { return false; } -function renderIconInRunsTable(cell, formatterParams, onRendered){ +function renderIconInRunsTable(cell, formatterParams, onRendered) { let value = cell.getValue(); - console.log(value) + console.log(value); switch (value) { - case 'running': - return '
running
' + case "running": + return '
running
'; - case 'finished': - return '
finished
' + case "waiting": + return '
waiting
'; + + case "finished": + return '
finished
'; default: - return '
unknown
' + return '
unknown
'; } } function initializeDashboardRunsPage() { //TODO: fetch runs data from server - fetch('/api/runs/all') - .then(response => { + fetch("/api/runs/all") + .then((response) => { if (!response.ok) { - throw new Error('Failed to fetch runs data'); + throw new Error("Failed to fetch runs data"); } return response.json(); }) - .then(data => { + .then((data) => { var runsTable = new Tabulator("#runs-table", { data: data, columns: [ - {title: "Status", field: "status", editor: false, vertAlign:"middle", formatter: renderIconInRunsTable}, - {title: "ID", field: "id", editor: false, vertAlign:"middle"}, - {title: "Project", field: "project", editor: false, vertAlign:"middle"}, - {title: "Name", field: "name", editor: false, vertAlign:"middle"}, - {title: "Create time", field: "create_time", editor: false, vertAlign:"middle"}, + { + title: "Status", + field: "status", + editor: false, + vertAlign: "middle", + formatter: renderIconInRunsTable, + }, + { + title: "ID", + field: "id", + editor: false, + vertAlign: "middle", + }, + { + title: "Project", + field: "project", + editor: false, + vertAlign: "middle", + }, + { + title: "Name", + field: "name", + editor: false, + vertAlign: "middle", + }, + { + title: "Create time", + field: "create_time", + editor: false, + vertAlign: "middle", + }, ], layout: "fitColumns", - }) + }); // Search logic - document.getElementById('runs-search-input').addEventListener('input', function(e) { - let searchValue = e.target.value; - if (searchValue) { - // Filter the table - runsTable.setFilter(allColumnFilter); - } else { - //Clear the filter - runsTable.clearFilter(); - } - }); + document + .getElementById("runs-search-input") + .addEventListener("input", function (e) { + let searchValue = e.target.value; + if (searchValue) { + // Filter the table + runsTable.setFilter(allColumnFilter); + } else { + //Clear the filter + runsTable.clearFilter(); + } + }); // Set up row click event runsTable.on("rowClick", function (e, row) { // Jump to the run detail page - loadDetailPageInDashboardContent('static/html/dashboard-detail.html', 'static/js/dashboard-detail.js', row.getData()); + loadDetailPageInDashboardContent( + "static/html/dashboard-detail.html", + "static/js/dashboard-detail.js", + row.getData() + ); }); }) - .catch(error => { + .catch((error) => { console.error(error); }); -} \ No newline at end of file +} diff --git a/src/agentscope/web/studio/static/js/dashboard.js b/src/agentscope/web/studio/static/js/dashboard.js index 44a3e18a0..60cd37c15 100644 --- a/src/agentscope/web/studio/static/js/dashboard.js +++ b/src/agentscope/web/studio/static/js/dashboard.js @@ -1,24 +1,24 @@ function loadRunsPageInDashboardContent(pageUrl, javascriptUrl) { fetch(pageUrl) - .then(response => { + .then((response) => { if (!response.ok) { - throw new Error('Connection error, cannot load the web page.'); + throw new Error("Connection error, cannot load the web page."); } return response.text(); }) - .then(html => { + .then((html) => { // Load the page content - document.getElementById('dashboard-content').innerHTML = html; + document.getElementById("dashboard-content").innerHTML = html; // Load the javascript file if (!isScriptLoaded(javascriptUrl)) { console.log("Loading script from " + javascriptUrl + "..."); - let script = document.createElement('script'); + let script = document.createElement("script"); script.src = javascriptUrl; - script.onload = function() { + script.onload = function () { // Initialize the runs tables initializeDashboardRunsPage(); - } + }; document.head.appendChild(script); } else { // Initialize the runs tables @@ -27,33 +27,35 @@ function loadRunsPageInDashboardContent(pageUrl, javascriptUrl) { // Update the title bar of the dashboard page let titleBar = document.getElementById("dashboard-titlebar"); - titleBar.innerHTML = "Dashboard"; + titleBar.innerHTML = + "Dashboard"; }) - .catch(error => { - console.error('Error encountered while loading page: ', error); - document.getElementById('content').innerHTML = '

Loading failed.

' + error; + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = + "

Loading failed.

" + error; }); } function loadDetailPageInDashboardContent(pageUrl, javascriptUrl, runtimeInfo) { fetch(pageUrl) - .then(response => { + .then((response) => { if (!response.ok) { - throw new Error('Connection error, cannot load the web page.'); + throw new Error("Connection error, cannot load the web page."); } return response.text(); }) - .then(html => { + .then((html) => { // Load the page content - document.getElementById('dashboard-content').innerHTML = html; + document.getElementById("dashboard-content").innerHTML = html; if (!isScriptLoaded(javascriptUrl)) { console.log("Loading script from " + javascriptUrl + "..."); - let script = document.createElement('script'); + let script = document.createElement("script"); script.src = javascriptUrl; - script.onload = function() { + script.onload = function () { initializeDashboardDetailPage(runtimeInfo); - } + }; document.head.appendChild(script); } else { initializeDashboardDetailPage(runtimeInfo); @@ -62,19 +64,25 @@ function loadDetailPageInDashboardContent(pageUrl, javascriptUrl, runtimeInfo) { // Update the title bar of the dashboard page let titleBar = document.getElementById("dashboard-titlebar"); // TODO: Add link to the dashboard page - titleBar.innerHTML = "Dashboard" + + titleBar.innerHTML = + "Dashboard" + '' + - "Runtime ID: "+runtimeInfo.id+"" - + "Runtime ID: " + + runtimeInfo.id + + ""; }) - .catch(error => { - console.error('Error encountered while loading page: ', error); - document.getElementById('content').innerHTML = '

Loading failed.

' + error; + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = + "

Loading failed.

" + error; }); } // Initialize the dashboard page with a table of runtime instances function initializeDashboardPage() { // The default content of the dashboard page - loadRunsPageInDashboardContent('static/html/dashboard-runs.html', 'static/js/dashboard-runs.js'); + loadRunsPageInDashboardContent( + "static/html/dashboard-runs.html", + "static/js/dashboard-runs.js" + ); } diff --git a/src/agentscope/web/studio/static/js/index.js b/src/agentscope/web/studio/static/js/index.js index 55fac98fb..95b952124 100644 --- a/src/agentscope/web/studio/static/js/index.js +++ b/src/agentscope/web/studio/static/js/index.js @@ -1,8 +1,8 @@ -const dashboardTabBtn = document.getElementById('dashboard-tab-btn'); -const workstationTabBtn = document.getElementById('workstation-tab-btn') -const marketTabBtn = document.getElementById('market-tab-btn') -const serverTabBtn = document.getElementById('server-tab-btn'); -const navigationBar = document.getElementById('navigation-bar'); +const dashboardTabBtn = document.getElementById("dashboard-tab-btn"); +const workstationTabBtn = document.getElementById("workstation-tab-btn"); +const marketTabBtn = document.getElementById("market-tab-btn"); +const serverTabBtn = document.getElementById("server-tab-btn"); +const navigationBar = document.getElementById("navigation-bar"); let currentPageUrl = null; let inGuidePage = true; @@ -10,7 +10,7 @@ let inGuidePage = true; let activeExpanded = false; // The current language of AgentScope Studio -let currentLang = "en" +let currentLang = "en"; // TODO: add button to switch language loadLang(currentLang); @@ -18,31 +18,35 @@ loadLang(currentLang); // Load the language file function loadLang(lang) { fetch(`static/json/language.json`) - .then(response => response.json()) - .then(data => { + .then((response) => response.json()) + .then((data) => { console.log("Begin to load language file"); - document.querySelectorAll("[data-i18n]").forEach(el => { + document.querySelectorAll("[data-i18n]").forEach((el) => { const key = el.getAttribute("data-i18n"); - console.log("Replace " + key) + console.log("Replace " + key); el.innerText = data[lang][key] || "Undefined"; }); console.log("Finish loading language file"); }) - .catch(error => console.error('Error loading the language file:', error)); + .catch((error) => + console.error("Error loading the language file:", error) + ); } - // Check if the script is already loaded function isScriptLoaded(src) { - return Array.from(document.scripts).some(script => { - return new URL(script.src).pathname === new URL(src, window.location.href).pathname; + return Array.from(document.scripts).some((script) => { + return ( + new URL(script.src).pathname === + new URL(src, window.location.href).pathname + ); }); } // After loading different pages, we need to call the initialization function of this page function initializeTabPageByUrl(pageUrl) { switch (pageUrl) { - case 'static/html/dashboard.html': + case "static/html/dashboard.html": initializeDashboardPage(); break; } @@ -50,37 +54,36 @@ function initializeTabPageByUrl(pageUrl) { // Loading different pages in index.html function loadTabPage(pageUrl, javascriptUrl) { - if (currentPageUrl === pageUrl) { - return; - } + console.log("current tab page " + currentPageUrl); + console.log("load tab page " + pageUrl); fetch(pageUrl) - .then(response => { + .then((response) => { if (!response.ok) { - throw new Error('Connection error, cannot load the web page.'); + throw new Error("Connection error, cannot load the web page."); } return response.text(); }) - .then(html => { + .then((html) => { currentPageUrl = pageUrl; // Hide the sidebar for other pages except the guide page - if (pageUrl === 'static/html/index-guide.html') { - navigationBar.classList.remove('collapsed'); + if (pageUrl === "static/html/index-guide.html") { + navigationBar.classList.remove("collapsed"); inGuidePage = true; } else { - navigationBar.classList.add('collapsed'); + navigationBar.classList.add("collapsed"); inGuidePage = false; activeExpanded = false; } // Load the javascript file if (javascriptUrl && !isScriptLoaded(javascriptUrl)) { - let script = document.createElement('script'); + let script = document.createElement("script"); script.src = javascriptUrl; script.onload = function () { // The first time we must initialize the page within the onload function to ensure the script is loaded initializeTabPageByUrl(pageUrl); - } + }; document.head.appendChild(script); } else { // If is not the first time, we can directly call the initialization function @@ -88,60 +91,60 @@ function loadTabPage(pageUrl, javascriptUrl) { } // Load the page content - document.getElementById('content').innerHTML = html; + document.getElementById("content").innerHTML = html; // switch selected status of the tab buttons switch (pageUrl) { - case 'static/html/dashboard.html': - dashboardTabBtn.classList.add('selected'); - workstationTabBtn.classList.remove('selected'); - marketTabBtn.classList.remove('selected'); - serverTabBtn.classList.remove('selected'); + case "static/html/dashboard.html": + dashboardTabBtn.classList.add("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.remove("selected"); break; - case 'static/html/workstation.html': - dashboardTabBtn.classList.remove('selected'); - workstationTabBtn.classList.add('selected'); - marketTabBtn.classList.remove('selected'); - serverTabBtn.classList.remove('selected'); + case "static/html/workstation.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.add("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.remove("selected"); break; - case 'static/html/market.html': - dashboardTabBtn.classList.remove('selected'); - workstationTabBtn.classList.remove('selected'); - marketTabBtn.classList.add('selected'); - serverTabBtn.classList.remove('selected'); + case "static/html/market.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.add("selected"); + serverTabBtn.classList.remove("selected"); break; - case 'static/html/server.html': - dashboardTabBtn.classList.remove('selected'); - workstationTabBtn.classList.remove('selected'); - marketTabBtn.classList.remove('selected'); - serverTabBtn.classList.add('selected'); + case "static/html/server.html": + dashboardTabBtn.classList.remove("selected"); + workstationTabBtn.classList.remove("selected"); + marketTabBtn.classList.remove("selected"); + serverTabBtn.classList.add("selected"); break; - } }) - .catch(error => { - console.error('Error encountered while loading page: ', error); - document.getElementById('content').innerHTML = '

Loading failed.

' + error; + .catch((error) => { + console.error("Error encountered while loading page: ", error); + document.getElementById("content").innerHTML = + "

Loading failed.

" + error; }); } -loadTabPage('static/html/index-guide.html', null); +loadTabPage("static/html/index-guide.html", null); -navigationBar.addEventListener('mouseenter', function () { +navigationBar.addEventListener("mouseenter", function () { if (activeExpanded) { - navigationBar.classList.remove('collapsed'); + navigationBar.classList.remove("collapsed"); } -}) +}); -navigationBar.addEventListener('mouseleave', function () { +navigationBar.addEventListener("mouseleave", function () { // In guide page, the navigation bar will not be collapsed if (!inGuidePage) { // Collapse the navigation bar when the mouse leaves the navigation bar - navigationBar.classList.add('collapsed'); + navigationBar.classList.add("collapsed"); // Allow the navigation bar to be expanded when the mouse leaves the navigation bar to avoid expanding right after collapsing (when not finished collapsing yet) activeExpanded = true; } -}) \ No newline at end of file +}); From e0ac80a5145aa3eb247a4cb3ca5d6028706d692d Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Thu, 23 May 2024 11:34:32 +0800 Subject: [PATCH 29/80] add socketio js --- src/agentscope/web/studio/static/js/index.js | 2 - .../web/studio/static/js/socket.io.js | 7918 +++++++++++++++++ 2 files changed, 7918 insertions(+), 2 deletions(-) create mode 100644 src/agentscope/web/studio/static/js/socket.io.js diff --git a/src/agentscope/web/studio/static/js/index.js b/src/agentscope/web/studio/static/js/index.js index 95b952124..34b36cd52 100644 --- a/src/agentscope/web/studio/static/js/index.js +++ b/src/agentscope/web/studio/static/js/index.js @@ -54,8 +54,6 @@ function initializeTabPageByUrl(pageUrl) { // Loading different pages in index.html function loadTabPage(pageUrl, javascriptUrl) { - console.log("current tab page " + currentPageUrl); - console.log("load tab page " + pageUrl); fetch(pageUrl) .then((response) => { if (!response.ok) { diff --git a/src/agentscope/web/studio/static/js/socket.io.js b/src/agentscope/web/studio/static/js/socket.io.js new file mode 100644 index 000000000..b28d6bb1f --- /dev/null +++ b/src/agentscope/web/studio/static/js/socket.io.js @@ -0,0 +1,7918 @@ +/*! + * Socket.IO v3.1.3 + * (c) 2014-2021 Guillermo Rauch + * Released under the MIT License. + */ +(function webpackUniversalModuleDefinition(root, factory) { + if (typeof exports === "object" && typeof module === "object") + module.exports = factory(); + else if (typeof define === "function" && define.amd) define([], factory); + else if (typeof exports === "object") exports["io"] = factory(); + else root["io"] = factory(); +})(self, function () { + return /******/ (function (modules) { + // webpackBootstrap + /******/ // The module cache + /******/ var installedModules = {}; + /******/ + /******/ // The require function + /******/ function __webpack_require__(moduleId) { + /******/ + /******/ // Check if module is in cache + /******/ if (installedModules[moduleId]) { + /******/ return installedModules[moduleId].exports; + /******/ + } + /******/ // Create a new module (and put it into the cache) + /******/ var module = (installedModules[moduleId] = { + /******/ i: moduleId, + /******/ l: false, + /******/ exports: {}, + /******/ + }); + /******/ + /******/ // Execute the module function + /******/ modules[moduleId].call( + module.exports, + module, + module.exports, + __webpack_require__ + ); + /******/ + /******/ // Flag the module as loaded + /******/ module.l = true; + /******/ + /******/ // Return the exports of the module + /******/ return module.exports; + /******/ + } + /******/ + /******/ + /******/ // expose the modules object (__webpack_modules__) + /******/ __webpack_require__.m = modules; + /******/ + /******/ // expose the module cache + /******/ __webpack_require__.c = installedModules; + /******/ + /******/ // define getter function for harmony exports + /******/ __webpack_require__.d = function (exports, name, getter) { + /******/ if (!__webpack_require__.o(exports, name)) { + /******/ Object.defineProperty(exports, name, { + enumerable: true, + get: getter, + }); + /******/ + } + /******/ + }; + /******/ + /******/ // define __esModule on exports + /******/ __webpack_require__.r = function (exports) { + /******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) { + /******/ Object.defineProperty(exports, Symbol.toStringTag, { + value: "Module", + }); + /******/ + } + /******/ Object.defineProperty(exports, "__esModule", { value: true }); + /******/ + }; + /******/ + /******/ // create a fake namespace object + /******/ // mode & 1: value is a module id, require it + /******/ // mode & 2: merge all properties of value into the ns + /******/ // mode & 4: return value when already ns object + /******/ // mode & 8|1: behave like require + /******/ __webpack_require__.t = function (value, mode) { + /******/ if (mode & 1) value = __webpack_require__(value); + /******/ if (mode & 8) return value; + /******/ if ( + mode & 4 && + typeof value === "object" && + value && + value.__esModule + ) + return value; + /******/ var ns = Object.create(null); + /******/ __webpack_require__.r(ns); + /******/ Object.defineProperty(ns, "default", { + enumerable: true, + value: value, + }); + /******/ if (mode & 2 && typeof value != "string") + for (var key in value) + __webpack_require__.d( + ns, + key, + function (key) { + return value[key]; + }.bind(null, key) + ); + /******/ return ns; + /******/ + }; + /******/ + /******/ // getDefaultExport function for compatibility with non-harmony modules + /******/ __webpack_require__.n = function (module) { + /******/ var getter = + module && module.__esModule + ? /******/ function getDefault() { + return module["default"]; + } + : /******/ function getModuleExports() { + return module; + }; + /******/ __webpack_require__.d(getter, "a", getter); + /******/ return getter; + /******/ + }; + /******/ + /******/ // Object.prototype.hasOwnProperty.call + /******/ __webpack_require__.o = function (object, property) { + return Object.prototype.hasOwnProperty.call(object, property); + }; + /******/ + /******/ // __webpack_public_path__ + /******/ __webpack_require__.p = ""; + /******/ + /******/ + /******/ // Load entry module and return exports + /******/ return __webpack_require__( + (__webpack_require__.s = "./build/index.js") + ); + /******/ + })( + /************************************************************************/ + /******/ { + /***/ "./build/index.js": + /*!************************!*\ + !*** ./build/index.js ***! + \************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.Socket = + exports.io = + exports.Manager = + exports.protocol = + void 0; + + var url_1 = __webpack_require__(/*! ./url */ "./build/url.js"); + + var manager_1 = __webpack_require__( + /*! ./manager */ "./build/manager.js" + ); + + var socket_1 = __webpack_require__( + /*! ./socket */ "./build/socket.js" + ); + + Object.defineProperty(exports, "Socket", { + enumerable: true, + get: function get() { + return socket_1.Socket; + }, + }); + + var debug = __webpack_require__( + /*! debug */ "./node_modules/debug/src/browser.js" + )("socket.io-client"); + /** + * Module exports. + */ + + module.exports = exports = lookup; + /** + * Managers cache. + */ + + var cache = (exports.managers = {}); + + function lookup(uri, opts) { + if (_typeof(uri) === "object") { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + var parsed = url_1.url(uri, opts.path); + var source = parsed.source; + var id = parsed.id; + var path = parsed.path; + var sameNamespace = cache[id] && path in cache[id]["nsps"]; + var newConnection = + opts.forceNew || + opts["force new connection"] || + false === opts.multiplex || + sameNamespace; + var io; + + if (newConnection) { + debug("ignoring socket cache for %s", source); + io = new manager_1.Manager(source, opts); + } else { + if (!cache[id]) { + debug("new io instance for %s", source); + cache[id] = new manager_1.Manager(source, opts); + } + + io = cache[id]; + } + + if (parsed.query && !opts.query) { + opts.query = parsed.queryKey; + } + + return io.socket(parsed.path, opts); + } + + exports.io = lookup; + /** + * Protocol version. + * + * @public + */ + + var socket_io_parser_1 = __webpack_require__( + /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" + ); + + Object.defineProperty(exports, "protocol", { + enumerable: true, + get: function get() { + return socket_io_parser_1.protocol; + }, + }); + /** + * `connect`. + * + * @param {String} uri + * @public + */ + + exports.connect = lookup; + /** + * Expose constructors for standalone build. + * + * @public + */ + + var manager_2 = __webpack_require__( + /*! ./manager */ "./build/manager.js" + ); + + Object.defineProperty(exports, "Manager", { + enumerable: true, + get: function get() { + return manager_2.Manager; + }, + }); + + /***/ + }, + + /***/ "./build/manager.js": + /*!**************************!*\ + !*** ./build/manager.js ***! + \**************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) + _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _get(target, property, receiver) { + if (typeof Reflect !== "undefined" && Reflect.get) { + _get = Reflect.get; + } else { + _get = function _get(target, property, receiver) { + var base = _superPropBase(target, property); + if (!base) return; + var desc = Object.getOwnPropertyDescriptor(base, property); + if (desc.get) { + return desc.get.call(receiver); + } + return desc.value; + }; + } + return _get(target, property, receiver || target); + } + + function _superPropBase(object, property) { + while (!Object.prototype.hasOwnProperty.call(object, property)) { + object = _getPrototypeOf(object); + if (object === null) break; + } + return object; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError( + "Super expression must either be null or a function" + ); + } + subClass.prototype = Object.create( + superClass && superClass.prototype, + { + constructor: { + value: subClass, + writable: true, + configurable: true, + }, + } + ); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = + Object.setPrototypeOf || + function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + + function _possibleConstructorReturn(self, call) { + if ( + call && + (_typeof(call) === "object" || typeof call === "function") + ) { + return call; + } + return _assertThisInitialized(self); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + } + return self; + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) + return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Date.prototype.toString.call( + Reflect.construct(Date, [], function () {}) + ); + return true; + } catch (e) { + return false; + } + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf + : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.Manager = void 0; + + var eio = __webpack_require__( + /*! engine.io-client */ "./node_modules/engine.io-client/lib/index.js" + ); + + var socket_1 = __webpack_require__( + /*! ./socket */ "./build/socket.js" + ); + + var Emitter = __webpack_require__( + /*! component-emitter */ "./node_modules/component-emitter/index.js" + ); + + var parser = __webpack_require__( + /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" + ); + + var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); + + var Backoff = __webpack_require__( + /*! backo2 */ "./node_modules/backo2/index.js" + ); + + var debug = __webpack_require__( + /*! debug */ "./node_modules/debug/src/browser.js" + )("socket.io-client:manager"); + + var Manager = /*#__PURE__*/ (function (_Emitter) { + _inherits(Manager, _Emitter); + + var _super = _createSuper(Manager); + + function Manager(uri, opts) { + var _this; + + _classCallCheck(this, Manager); + + _this = _super.call(this); + _this.nsps = {}; + _this.subs = []; + + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = undefined; + } + + opts = opts || {}; + opts.path = opts.path || "/socket.io"; + _this.opts = opts; + + _this.reconnection(opts.reconnection !== false); + + _this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); + + _this.reconnectionDelay(opts.reconnectionDelay || 1000); + + _this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); + + _this.randomizationFactor(opts.randomizationFactor || 0.5); + + _this.backoff = new Backoff({ + min: _this.reconnectionDelay(), + max: _this.reconnectionDelayMax(), + jitter: _this.randomizationFactor(), + }); + + _this.timeout(null == opts.timeout ? 20000 : opts.timeout); + + _this._readyState = "closed"; + _this.uri = uri; + + var _parser = opts.parser || parser; + + _this.encoder = new _parser.Encoder(); + _this.decoder = new _parser.Decoder(); + _this._autoConnect = opts.autoConnect !== false; + if (_this._autoConnect) _this.open(); + return _this; + } + + _createClass(Manager, [ + { + key: "reconnection", + value: function reconnection(v) { + if (!arguments.length) return this._reconnection; + this._reconnection = !!v; + return this; + }, + }, + { + key: "reconnectionAttempts", + value: function reconnectionAttempts(v) { + if (v === undefined) return this._reconnectionAttempts; + this._reconnectionAttempts = v; + return this; + }, + }, + { + key: "reconnectionDelay", + value: function reconnectionDelay(v) { + var _a; + + if (v === undefined) return this._reconnectionDelay; + this._reconnectionDelay = v; + (_a = this.backoff) === null || _a === void 0 + ? void 0 + : _a.setMin(v); + return this; + }, + }, + { + key: "randomizationFactor", + value: function randomizationFactor(v) { + var _a; + + if (v === undefined) return this._randomizationFactor; + this._randomizationFactor = v; + (_a = this.backoff) === null || _a === void 0 + ? void 0 + : _a.setJitter(v); + return this; + }, + }, + { + key: "reconnectionDelayMax", + value: function reconnectionDelayMax(v) { + var _a; + + if (v === undefined) return this._reconnectionDelayMax; + this._reconnectionDelayMax = v; + (_a = this.backoff) === null || _a === void 0 + ? void 0 + : _a.setMax(v); + return this; + }, + }, + { + key: "timeout", + value: function timeout(v) { + if (!arguments.length) return this._timeout; + this._timeout = v; + return this; + }, + /** + * Starts trying to reconnect if reconnection is enabled and we have not + * started reconnecting yet + * + * @private + */ + }, + { + key: "maybeReconnectOnOpen", + value: function maybeReconnectOnOpen() { + // Only try to reconnect if it's the first time we're connecting + if ( + !this._reconnecting && + this._reconnection && + this.backoff.attempts === 0 + ) { + // keeps reconnection from firing twice for the same reconnection loop + this.reconnect(); + } + }, + /** + * Sets the current transport `socket`. + * + * @param {Function} fn - optional, callback + * @return self + * @public + */ + }, + { + key: "open", + value: function open(fn) { + var _this2 = this; + + debug("readyState %s", this._readyState); + if (~this._readyState.indexOf("open")) return this; + debug("opening %s", this.uri); + this.engine = eio(this.uri, this.opts); + var socket = this.engine; + var self = this; + this._readyState = "opening"; + this.skipReconnect = false; // emit `open` + + var openSubDestroy = on_1.on(socket, "open", function () { + self.onopen(); + fn && fn(); + }); // emit `error` + + var errorSub = on_1.on(socket, "error", function (err) { + debug("error"); + self.cleanup(); + self._readyState = "closed"; + + _get( + _getPrototypeOf(Manager.prototype), + "emit", + _this2 + ).call(_this2, "error", err); + + if (fn) { + fn(err); + } else { + // Only do this if there is no fn to handle the error + self.maybeReconnectOnOpen(); + } + }); + + if (false !== this._timeout) { + var timeout = this._timeout; + debug("connect attempt will timeout after %d", timeout); + + if (timeout === 0) { + openSubDestroy(); // prevents a race condition with the 'open' event + } // set timer + + var timer = setTimeout(function () { + debug("connect attempt timed out after %d", timeout); + openSubDestroy(); + socket.close(); + socket.emit("error", new Error("timeout")); + }, timeout); + this.subs.push(function subDestroy() { + clearTimeout(timer); + }); + } + + this.subs.push(openSubDestroy); + this.subs.push(errorSub); + return this; + }, + /** + * Alias for open() + * + * @return self + * @public + */ + }, + { + key: "connect", + value: function connect(fn) { + return this.open(fn); + }, + /** + * Called upon transport open. + * + * @private + */ + }, + { + key: "onopen", + value: function onopen() { + debug("open"); // clear old subs + + this.cleanup(); // mark as open + + this._readyState = "open"; + + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "open" + ); // add new subs + + var socket = this.engine; + this.subs.push( + on_1.on(socket, "ping", this.onping.bind(this)), + on_1.on(socket, "data", this.ondata.bind(this)), + on_1.on(socket, "error", this.onerror.bind(this)), + on_1.on(socket, "close", this.onclose.bind(this)), + on_1.on(this.decoder, "decoded", this.ondecoded.bind(this)) + ); + }, + /** + * Called upon a ping. + * + * @private + */ + }, + { + key: "onping", + value: function onping() { + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "ping" + ); + }, + /** + * Called with data. + * + * @private + */ + }, + { + key: "ondata", + value: function ondata(data) { + this.decoder.add(data); + }, + /** + * Called when parser fully decodes a packet. + * + * @private + */ + }, + { + key: "ondecoded", + value: function ondecoded(packet) { + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "packet", + packet + ); + }, + /** + * Called upon socket error. + * + * @private + */ + }, + { + key: "onerror", + value: function onerror(err) { + debug("error", err); + + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "error", + err + ); + }, + /** + * Creates a new socket for the given `nsp`. + * + * @return {Socket} + * @public + */ + }, + { + key: "socket", + value: function socket(nsp, opts) { + var socket = this.nsps[nsp]; + + if (!socket) { + socket = new socket_1.Socket(this, nsp, opts); + this.nsps[nsp] = socket; + } + + return socket; + }, + /** + * Called upon a socket close. + * + * @param socket + * @private + */ + }, + { + key: "_destroy", + value: function _destroy(socket) { + var nsps = Object.keys(this.nsps); + + for (var _i = 0, _nsps = nsps; _i < _nsps.length; _i++) { + var nsp = _nsps[_i]; + var _socket = this.nsps[nsp]; + + if (_socket.active) { + debug("socket %s is still active, skipping close", nsp); + return; + } + } + + this._close(); + }, + /** + * Writes a packet. + * + * @param packet + * @private + */ + }, + { + key: "_packet", + value: function _packet(packet) { + debug("writing packet %j", packet); + var encodedPackets = this.encoder.encode(packet); + + for (var i = 0; i < encodedPackets.length; i++) { + this.engine.write(encodedPackets[i], packet.options); + } + }, + /** + * Clean up transport subscriptions and packet buffer. + * + * @private + */ + }, + { + key: "cleanup", + value: function cleanup() { + debug("cleanup"); + this.subs.forEach(function (subDestroy) { + return subDestroy(); + }); + this.subs.length = 0; + this.decoder.destroy(); + }, + /** + * Close the current socket. + * + * @private + */ + }, + { + key: "_close", + value: function _close() { + debug("disconnect"); + this.skipReconnect = true; + this._reconnecting = false; + + if ("opening" === this._readyState) { + // `onclose` will not fire because + // an open event never happened + this.cleanup(); + } + + this.backoff.reset(); + this._readyState = "closed"; + if (this.engine) this.engine.close(); + }, + /** + * Alias for close() + * + * @private + */ + }, + { + key: "disconnect", + value: function disconnect() { + return this._close(); + }, + /** + * Called upon engine close. + * + * @private + */ + }, + { + key: "onclose", + value: function onclose(reason) { + debug("onclose"); + this.cleanup(); + this.backoff.reset(); + this._readyState = "closed"; + + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "close", + reason + ); + + if (this._reconnection && !this.skipReconnect) { + this.reconnect(); + } + }, + /** + * Attempt a reconnection. + * + * @private + */ + }, + { + key: "reconnect", + value: function reconnect() { + var _this3 = this; + + if (this._reconnecting || this.skipReconnect) return this; + var self = this; + + if (this.backoff.attempts >= this._reconnectionAttempts) { + debug("reconnect failed"); + this.backoff.reset(); + + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "reconnect_failed" + ); + + this._reconnecting = false; + } else { + var delay = this.backoff.duration(); + debug("will wait %dms before reconnect attempt", delay); + this._reconnecting = true; + var timer = setTimeout(function () { + if (self.skipReconnect) return; + debug("attempting reconnect"); + + _get( + _getPrototypeOf(Manager.prototype), + "emit", + _this3 + ).call( + _this3, + "reconnect_attempt", + self.backoff.attempts + ); // check again for the case socket closed in above events + + if (self.skipReconnect) return; + self.open(function (err) { + if (err) { + debug("reconnect attempt error"); + self._reconnecting = false; + self.reconnect(); + + _get( + _getPrototypeOf(Manager.prototype), + "emit", + _this3 + ).call(_this3, "reconnect_error", err); + } else { + debug("reconnect success"); + self.onreconnect(); + } + }); + }, delay); + this.subs.push(function subDestroy() { + clearTimeout(timer); + }); + } + }, + /** + * Called upon successful reconnect. + * + * @private + */ + }, + { + key: "onreconnect", + value: function onreconnect() { + var attempt = this.backoff.attempts; + this._reconnecting = false; + this.backoff.reset(); + + _get(_getPrototypeOf(Manager.prototype), "emit", this).call( + this, + "reconnect", + attempt + ); + }, + }, + ]); + + return Manager; + })(Emitter); + + exports.Manager = Manager; + + /***/ + }, + + /***/ "./build/on.js": + /*!*********************!*\ + !*** ./build/on.js ***! + \*********************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.on = void 0; + + function on(obj, ev, fn) { + obj.on(ev, fn); + return function subDestroy() { + obj.off(ev, fn); + }; + } + + exports.on = on; + + /***/ + }, + + /***/ "./build/socket.js": + /*!*************************!*\ + !*** ./build/socket.js ***! + \*************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + function _createForOfIteratorHelper(o, allowArrayLike) { + var it; + if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { + if ( + Array.isArray(o) || + (it = _unsupportedIterableToArray(o)) || + (allowArrayLike && o && typeof o.length === "number") + ) { + if (it) o = it; + var i = 0; + var F = function F() {}; + return { + s: F, + n: function n() { + if (i >= o.length) return { done: true }; + return { done: false, value: o[i++] }; + }, + e: function e(_e) { + throw _e; + }, + f: F, + }; + } + throw new TypeError( + "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." + ); + } + var normalCompletion = true, + didErr = false, + err; + return { + s: function s() { + it = o[Symbol.iterator](); + }, + n: function n() { + var step = it.next(); + normalCompletion = step.done; + return step; + }, + e: function e(_e2) { + didErr = true; + err = _e2; + }, + f: function f() { + try { + if (!normalCompletion && it["return"] != null) it["return"](); + } finally { + if (didErr) throw err; + } + }, + }; + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if ( + n === "Arguments" || + /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) + ) + return _arrayLikeToArray(o, minLen); + } + + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + return arr2; + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) + _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _get(target, property, receiver) { + if (typeof Reflect !== "undefined" && Reflect.get) { + _get = Reflect.get; + } else { + _get = function _get(target, property, receiver) { + var base = _superPropBase(target, property); + if (!base) return; + var desc = Object.getOwnPropertyDescriptor(base, property); + if (desc.get) { + return desc.get.call(receiver); + } + return desc.value; + }; + } + return _get(target, property, receiver || target); + } + + function _superPropBase(object, property) { + while (!Object.prototype.hasOwnProperty.call(object, property)) { + object = _getPrototypeOf(object); + if (object === null) break; + } + return object; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError( + "Super expression must either be null or a function" + ); + } + subClass.prototype = Object.create( + superClass && superClass.prototype, + { + constructor: { + value: subClass, + writable: true, + configurable: true, + }, + } + ); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = + Object.setPrototypeOf || + function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + + function _possibleConstructorReturn(self, call) { + if ( + call && + (_typeof(call) === "object" || typeof call === "function") + ) { + return call; + } + return _assertThisInitialized(self); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + } + return self; + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) + return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Date.prototype.toString.call( + Reflect.construct(Date, [], function () {}) + ); + return true; + } catch (e) { + return false; + } + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf + : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.Socket = void 0; + + var socket_io_parser_1 = __webpack_require__( + /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" + ); + + var Emitter = __webpack_require__( + /*! component-emitter */ "./node_modules/component-emitter/index.js" + ); + + var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); + + var debug = __webpack_require__( + /*! debug */ "./node_modules/debug/src/browser.js" + )("socket.io-client:socket"); + /** + * Internal events. + * These events can't be emitted by the user. + */ + + var RESERVED_EVENTS = Object.freeze({ + connect: 1, + connect_error: 1, + disconnect: 1, + disconnecting: 1, + // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener + newListener: 1, + removeListener: 1, + }); + + var Socket = /*#__PURE__*/ (function (_Emitter) { + _inherits(Socket, _Emitter); + + var _super = _createSuper(Socket); + + /** + * `Socket` constructor. + * + * @public + */ + function Socket(io, nsp, opts) { + var _this; + + _classCallCheck(this, Socket); + + _this = _super.call(this); + _this.receiveBuffer = []; + _this.sendBuffer = []; + _this.ids = 0; + _this.acks = {}; + _this.flags = {}; + _this.io = io; + _this.nsp = nsp; + _this.ids = 0; + _this.acks = {}; + _this.receiveBuffer = []; + _this.sendBuffer = []; + _this.connected = false; + _this.disconnected = true; + _this.flags = {}; + + if (opts && opts.auth) { + _this.auth = opts.auth; + } + + if (_this.io._autoConnect) _this.open(); + return _this; + } + /** + * Subscribe to open, close and packet events + * + * @private + */ + + _createClass(Socket, [ + { + key: "subEvents", + value: function subEvents() { + if (this.subs) return; + var io = this.io; + this.subs = [ + on_1.on(io, "open", this.onopen.bind(this)), + on_1.on(io, "packet", this.onpacket.bind(this)), + on_1.on(io, "error", this.onerror.bind(this)), + on_1.on(io, "close", this.onclose.bind(this)), + ]; + }, + /** + * Whether the Socket will try to reconnect when its Manager connects or reconnects + */ + }, + { + key: "connect", + + /** + * "Opens" the socket. + * + * @public + */ + value: function connect() { + if (this.connected) return this; + this.subEvents(); + if (!this.io["_reconnecting"]) this.io.open(); // ensure open + + if ("open" === this.io._readyState) this.onopen(); + return this; + }, + /** + * Alias for connect() + */ + }, + { + key: "open", + value: function open() { + return this.connect(); + }, + /** + * Sends a `message` event. + * + * @return self + * @public + */ + }, + { + key: "send", + value: function send() { + for ( + var _len = arguments.length, + args = new Array(_len), + _key = 0; + _key < _len; + _key++ + ) { + args[_key] = arguments[_key]; + } + + args.unshift("message"); + this.emit.apply(this, args); + return this; + }, + /** + * Override `emit`. + * If the event is in `events`, it's emitted normally. + * + * @param ev - event name + * @return self + * @public + */ + }, + { + key: "emit", + value: function emit(ev) { + if (RESERVED_EVENTS.hasOwnProperty(ev)) { + throw new Error('"' + ev + '" is a reserved event name'); + } + + for ( + var _len2 = arguments.length, + args = new Array(_len2 > 1 ? _len2 - 1 : 0), + _key2 = 1; + _key2 < _len2; + _key2++ + ) { + args[_key2 - 1] = arguments[_key2]; + } + + args.unshift(ev); + var packet = { + type: socket_io_parser_1.PacketType.EVENT, + data: args, + }; + packet.options = {}; + packet.options.compress = this.flags.compress !== false; // event ack callback + + if ("function" === typeof args[args.length - 1]) { + debug("emitting packet with ack id %d", this.ids); + this.acks[this.ids] = args.pop(); + packet.id = this.ids++; + } + + var isTransportWritable = + this.io.engine && + this.io.engine.transport && + this.io.engine.transport.writable; + var discardPacket = + this.flags["volatile"] && + (!isTransportWritable || !this.connected); + + if (discardPacket) { + debug( + "discard packet as the transport is not currently writable" + ); + } else if (this.connected) { + this.packet(packet); + } else { + this.sendBuffer.push(packet); + } + + this.flags = {}; + return this; + }, + /** + * Sends a packet. + * + * @param packet + * @private + */ + }, + { + key: "packet", + value: function packet(_packet) { + _packet.nsp = this.nsp; + + this.io._packet(_packet); + }, + /** + * Called upon engine `open`. + * + * @private + */ + }, + { + key: "onopen", + value: function onopen() { + var _this2 = this; + + debug("transport is open - connecting"); + + if (typeof this.auth == "function") { + this.auth(function (data) { + _this2.packet({ + type: socket_io_parser_1.PacketType.CONNECT, + data: data, + }); + }); + } else { + this.packet({ + type: socket_io_parser_1.PacketType.CONNECT, + data: this.auth, + }); + } + }, + /** + * Called upon engine or manager `error`. + * + * @param err + * @private + */ + }, + { + key: "onerror", + value: function onerror(err) { + if (!this.connected) { + _get(_getPrototypeOf(Socket.prototype), "emit", this).call( + this, + "connect_error", + err + ); + } + }, + /** + * Called upon engine `close`. + * + * @param reason + * @private + */ + }, + { + key: "onclose", + value: function onclose(reason) { + debug("close (%s)", reason); + this.connected = false; + this.disconnected = true; + delete this.id; + + _get(_getPrototypeOf(Socket.prototype), "emit", this).call( + this, + "disconnect", + reason + ); + }, + /** + * Called with socket packet. + * + * @param packet + * @private + */ + }, + { + key: "onpacket", + value: function onpacket(packet) { + var sameNamespace = packet.nsp === this.nsp; + if (!sameNamespace) return; + + switch (packet.type) { + case socket_io_parser_1.PacketType.CONNECT: + if (packet.data && packet.data.sid) { + var id = packet.data.sid; + this.onconnect(id); + } else { + _get( + _getPrototypeOf(Socket.prototype), + "emit", + this + ).call( + this, + "connect_error", + new Error( + "It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)" + ) + ); + } + + break; + + case socket_io_parser_1.PacketType.EVENT: + this.onevent(packet); + break; + + case socket_io_parser_1.PacketType.BINARY_EVENT: + this.onevent(packet); + break; + + case socket_io_parser_1.PacketType.ACK: + this.onack(packet); + break; + + case socket_io_parser_1.PacketType.BINARY_ACK: + this.onack(packet); + break; + + case socket_io_parser_1.PacketType.DISCONNECT: + this.ondisconnect(); + break; + + case socket_io_parser_1.PacketType.CONNECT_ERROR: + var err = new Error(packet.data.message); // @ts-ignore + + err.data = packet.data.data; + + _get( + _getPrototypeOf(Socket.prototype), + "emit", + this + ).call(this, "connect_error", err); + + break; + } + }, + /** + * Called upon a server event. + * + * @param packet + * @private + */ + }, + { + key: "onevent", + value: function onevent(packet) { + var args = packet.data || []; + debug("emitting event %j", args); + + if (null != packet.id) { + debug("attaching ack callback to event"); + args.push(this.ack(packet.id)); + } + + if (this.connected) { + this.emitEvent(args); + } else { + this.receiveBuffer.push(Object.freeze(args)); + } + }, + }, + { + key: "emitEvent", + value: function emitEvent(args) { + if (this._anyListeners && this._anyListeners.length) { + var listeners = this._anyListeners.slice(); + + var _iterator = _createForOfIteratorHelper(listeners), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done; ) { + var listener = _step.value; + listener.apply(this, args); + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + } + + _get(_getPrototypeOf(Socket.prototype), "emit", this).apply( + this, + args + ); + }, + /** + * Produces an ack callback to emit with an event. + * + * @private + */ + }, + { + key: "ack", + value: function ack(id) { + var self = this; + var sent = false; + return function () { + // prevent double callbacks + if (sent) return; + sent = true; + + for ( + var _len3 = arguments.length, + args = new Array(_len3), + _key3 = 0; + _key3 < _len3; + _key3++ + ) { + args[_key3] = arguments[_key3]; + } + + debug("sending ack %j", args); + self.packet({ + type: socket_io_parser_1.PacketType.ACK, + id: id, + data: args, + }); + }; + }, + /** + * Called upon a server acknowlegement. + * + * @param packet + * @private + */ + }, + { + key: "onack", + value: function onack(packet) { + var ack = this.acks[packet.id]; + + if ("function" === typeof ack) { + debug("calling ack %s with %j", packet.id, packet.data); + ack.apply(this, packet.data); + delete this.acks[packet.id]; + } else { + debug("bad ack %s", packet.id); + } + }, + /** + * Called upon server connect. + * + * @private + */ + }, + { + key: "onconnect", + value: function onconnect(id) { + debug("socket connected with id %s", id); + this.id = id; + this.connected = true; + this.disconnected = false; + + _get(_getPrototypeOf(Socket.prototype), "emit", this).call( + this, + "connect" + ); + + this.emitBuffered(); + }, + /** + * Emit buffered events (received and emitted). + * + * @private + */ + }, + { + key: "emitBuffered", + value: function emitBuffered() { + var _this3 = this; + + this.receiveBuffer.forEach(function (args) { + return _this3.emitEvent(args); + }); + this.receiveBuffer = []; + this.sendBuffer.forEach(function (packet) { + return _this3.packet(packet); + }); + this.sendBuffer = []; + }, + /** + * Called upon server disconnect. + * + * @private + */ + }, + { + key: "ondisconnect", + value: function ondisconnect() { + debug("server disconnect (%s)", this.nsp); + this.destroy(); + this.onclose("io server disconnect"); + }, + /** + * Called upon forced client/server side disconnections, + * this method ensures the manager stops tracking us and + * that reconnections don't get triggered for this. + * + * @private + */ + }, + { + key: "destroy", + value: function destroy() { + if (this.subs) { + // clean subscriptions to avoid reconnections + this.subs.forEach(function (subDestroy) { + return subDestroy(); + }); + this.subs = undefined; + } + + this.io["_destroy"](this); + }, + /** + * Disconnects the socket manually. + * + * @return self + * @public + */ + }, + { + key: "disconnect", + value: function disconnect() { + if (this.connected) { + debug("performing disconnect (%s)", this.nsp); + this.packet({ + type: socket_io_parser_1.PacketType.DISCONNECT, + }); + } // remove socket from pool + + this.destroy(); + + if (this.connected) { + // fire events + this.onclose("io client disconnect"); + } + + return this; + }, + /** + * Alias for disconnect() + * + * @return self + * @public + */ + }, + { + key: "close", + value: function close() { + return this.disconnect(); + }, + /** + * Sets the compress flag. + * + * @param compress - if `true`, compresses the sending data + * @return self + * @public + */ + }, + { + key: "compress", + value: function compress(_compress) { + this.flags.compress = _compress; + return this; + }, + /** + * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not + * ready to send messages. + * + * @returns self + * @public + */ + }, + { + key: "onAny", + + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. + * + * @param listener + * @public + */ + value: function onAny(listener) { + this._anyListeners = this._anyListeners || []; + + this._anyListeners.push(listener); + + return this; + }, + /** + * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the + * callback. The listener is added to the beginning of the listeners array. + * + * @param listener + * @public + */ + }, + { + key: "prependAny", + value: function prependAny(listener) { + this._anyListeners = this._anyListeners || []; + + this._anyListeners.unshift(listener); + + return this; + }, + /** + * Removes the listener that will be fired when any event is emitted. + * + * @param listener + * @public + */ + }, + { + key: "offAny", + value: function offAny(listener) { + if (!this._anyListeners) { + return this; + } + + if (listener) { + var listeners = this._anyListeners; + + for (var i = 0; i < listeners.length; i++) { + if (listener === listeners[i]) { + listeners.splice(i, 1); + return this; + } + } + } else { + this._anyListeners = []; + } + + return this; + }, + /** + * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, + * e.g. to remove listeners. + * + * @public + */ + }, + { + key: "listenersAny", + value: function listenersAny() { + return this._anyListeners || []; + }, + }, + { + key: "active", + get: function get() { + return !!this.subs; + }, + }, + { + key: "volatile", + get: function get() { + this.flags["volatile"] = true; + return this; + }, + }, + ]); + + return Socket; + })(Emitter); + + exports.Socket = Socket; + + /***/ + }, + + /***/ "./build/url.js": + /*!**********************!*\ + !*** ./build/url.js ***! + \**********************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true, + }); + exports.url = void 0; + + var parseuri = __webpack_require__( + /*! parseuri */ "./node_modules/parseuri/index.js" + ); + + var debug = __webpack_require__( + /*! debug */ "./node_modules/debug/src/browser.js" + )("socket.io-client:url"); + /** + * URL parser. + * + * @param uri - url + * @param path - the request path of the connection + * @param loc - An object meant to mimic window.location. + * Defaults to window.location. + * @public + */ + + function url(uri) { + var path = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : ""; + var loc = arguments.length > 2 ? arguments[2] : undefined; + var obj = uri; // default to window.location + + loc = loc || (typeof location !== "undefined" && location); + if (null == uri) uri = loc.protocol + "//" + loc.host; // relative path support + + if (typeof uri === "string") { + if ("/" === uri.charAt(0)) { + if ("/" === uri.charAt(1)) { + uri = loc.protocol + uri; + } else { + uri = loc.host + uri; + } + } + + if (!/^(https?|wss?):\/\//.test(uri)) { + debug("protocol-less url %s", uri); + + if ("undefined" !== typeof loc) { + uri = loc.protocol + "//" + uri; + } else { + uri = "https://" + uri; + } + } // parse + + debug("parse %s", uri); + obj = parseuri(uri); + } // make sure we treat `localhost:80` and `localhost` equally + + if (!obj.port) { + if (/^(http|ws)$/.test(obj.protocol)) { + obj.port = "80"; + } else if (/^(http|ws)s$/.test(obj.protocol)) { + obj.port = "443"; + } + } + + obj.path = obj.path || "/"; + var ipv6 = obj.host.indexOf(":") !== -1; + var host = ipv6 ? "[" + obj.host + "]" : obj.host; // define unique id + + obj.id = obj.protocol + "://" + host + ":" + obj.port + path; // define href + + obj.href = + obj.protocol + + "://" + + host + + (loc && loc.port === obj.port ? "" : ":" + obj.port); + return obj; + } + + exports.url = url; + + /***/ + }, + + /***/ "./node_modules/backo2/index.js": + /*!**************************************!*\ + !*** ./node_modules/backo2/index.js ***! + \**************************************/ + /*! no static exports found */ + /***/ function (module, exports) { + /** + * Expose `Backoff`. + */ + module.exports = Backoff; + /** + * Initialize backoff timer with `opts`. + * + * - `min` initial timeout in milliseconds [100] + * - `max` max timeout [10000] + * - `jitter` [0] + * - `factor` [2] + * + * @param {Object} opts + * @api public + */ + + function Backoff(opts) { + opts = opts || {}; + this.ms = opts.min || 100; + this.max = opts.max || 10000; + this.factor = opts.factor || 2; + this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; + this.attempts = 0; + } + /** + * Return the backoff duration. + * + * @return {Number} + * @api public + */ + + Backoff.prototype.duration = function () { + var ms = this.ms * Math.pow(this.factor, this.attempts++); + + if (this.jitter) { + var rand = Math.random(); + var deviation = Math.floor(rand * this.jitter * ms); + ms = + (Math.floor(rand * 10) & 1) == 0 + ? ms - deviation + : ms + deviation; + } + + return Math.min(ms, this.max) | 0; + }; + /** + * Reset the number of attempts. + * + * @api public + */ + + Backoff.prototype.reset = function () { + this.attempts = 0; + }; + /** + * Set the minimum duration + * + * @api public + */ + + Backoff.prototype.setMin = function (min) { + this.ms = min; + }; + /** + * Set the maximum duration + * + * @api public + */ + + Backoff.prototype.setMax = function (max) { + this.max = max; + }; + /** + * Set the jitter + * + * @api public + */ + + Backoff.prototype.setJitter = function (jitter) { + this.jitter = jitter; + }; + + /***/ + }, + + /***/ "./node_modules/component-emitter/index.js": + /*!*************************************************!*\ + !*** ./node_modules/component-emitter/index.js ***! + \*************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + /** + * Expose `Emitter`. + */ + if (true) { + module.exports = Emitter; + } + /** + * Initialize a new `Emitter`. + * + * @api public + */ + + function Emitter(obj) { + if (obj) return mixin(obj); + } + + /** + * Mixin the emitter properties. + * + * @param {Object} obj + * @return {Object} + * @api private + */ + + function mixin(obj) { + for (var key in Emitter.prototype) { + obj[key] = Emitter.prototype[key]; + } + + return obj; + } + /** + * Listen on the given `event` with `fn`. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.on = Emitter.prototype.addEventListener = function ( + event, + fn + ) { + this._callbacks = this._callbacks || {}; + (this._callbacks["$" + event] = + this._callbacks["$" + event] || []).push(fn); + return this; + }; + /** + * Adds an `event` listener that will be invoked a single + * time then automatically removed. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.once = function (event, fn) { + function on() { + this.off(event, on); + fn.apply(this, arguments); + } + + on.fn = fn; + this.on(event, on); + return this; + }; + /** + * Remove the given callback for `event` or all + * registered callbacks. + * + * @param {String} event + * @param {Function} fn + * @return {Emitter} + * @api public + */ + + Emitter.prototype.off = + Emitter.prototype.removeListener = + Emitter.prototype.removeAllListeners = + Emitter.prototype.removeEventListener = + function (event, fn) { + this._callbacks = this._callbacks || {}; // all + + if (0 == arguments.length) { + this._callbacks = {}; + return this; + } // specific event + + var callbacks = this._callbacks["$" + event]; + if (!callbacks) return this; // remove all handlers + + if (1 == arguments.length) { + delete this._callbacks["$" + event]; + return this; + } // remove specific handler + + var cb; + + for (var i = 0; i < callbacks.length; i++) { + cb = callbacks[i]; + + if (cb === fn || cb.fn === fn) { + callbacks.splice(i, 1); + break; + } + } // Remove event specific arrays for event types that no + // one is subscribed for to avoid memory leak. + + if (callbacks.length === 0) { + delete this._callbacks["$" + event]; + } + + return this; + }; + /** + * Emit `event` with the given args. + * + * @param {String} event + * @param {Mixed} ... + * @return {Emitter} + */ + + Emitter.prototype.emit = function (event) { + this._callbacks = this._callbacks || {}; + var args = new Array(arguments.length - 1), + callbacks = this._callbacks["$" + event]; + + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + + if (callbacks) { + callbacks = callbacks.slice(0); + + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + + return this; + }; + /** + * Return array of callbacks for `event`. + * + * @param {String} event + * @return {Array} + * @api public + */ + + Emitter.prototype.listeners = function (event) { + this._callbacks = this._callbacks || {}; + return this._callbacks["$" + event] || []; + }; + /** + * Check if this emitter has `event` handlers. + * + * @param {String} event + * @return {Boolean} + * @api public + */ + + Emitter.prototype.hasListeners = function (event) { + return !!this.listeners(event).length; + }; + + /***/ + }, + + /***/ "./node_modules/debug/src/browser.js": + /*!*******************************************!*\ + !*** ./node_modules/debug/src/browser.js ***! + \*******************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + /* eslint-env browser */ + + /** + * This is the web browser implementation of `debug()`. + */ + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = localstorage(); + + exports.destroy = (function () { + var warned = false; + return function () { + if (!warned) { + warned = true; + console.warn( + "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`." + ); + } + }; + })(); + /** + * Colors. + */ + + exports.colors = [ + "#0000CC", + "#0000FF", + "#0033CC", + "#0033FF", + "#0066CC", + "#0066FF", + "#0099CC", + "#0099FF", + "#00CC00", + "#00CC33", + "#00CC66", + "#00CC99", + "#00CCCC", + "#00CCFF", + "#3300CC", + "#3300FF", + "#3333CC", + "#3333FF", + "#3366CC", + "#3366FF", + "#3399CC", + "#3399FF", + "#33CC00", + "#33CC33", + "#33CC66", + "#33CC99", + "#33CCCC", + "#33CCFF", + "#6600CC", + "#6600FF", + "#6633CC", + "#6633FF", + "#66CC00", + "#66CC33", + "#9900CC", + "#9900FF", + "#9933CC", + "#9933FF", + "#99CC00", + "#99CC33", + "#CC0000", + "#CC0033", + "#CC0066", + "#CC0099", + "#CC00CC", + "#CC00FF", + "#CC3300", + "#CC3333", + "#CC3366", + "#CC3399", + "#CC33CC", + "#CC33FF", + "#CC6600", + "#CC6633", + "#CC9900", + "#CC9933", + "#CCCC00", + "#CCCC33", + "#FF0000", + "#FF0033", + "#FF0066", + "#FF0099", + "#FF00CC", + "#FF00FF", + "#FF3300", + "#FF3333", + "#FF3366", + "#FF3399", + "#FF33CC", + "#FF33FF", + "#FF6600", + "#FF6633", + "#FF9900", + "#FF9933", + "#FFCC00", + "#FFCC33", + ]; + /** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + // eslint-disable-next-line complexity + + function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if ( + typeof window !== "undefined" && + window.process && + (window.process.type === "renderer" || window.process.__nwjs) + ) { + return true; + } // Internet Explorer and Edge do not support colors. + + if ( + typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/) + ) { + return false; + } // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + + return ( + (typeof document !== "undefined" && + document.documentElement && + document.documentElement.style && + document.documentElement.style.WebkitAppearance) || // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== "undefined" && + window.console && + (window.console.firebug || + (window.console.exception && window.console.table))) || // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && + parseInt(RegExp.$1, 10) >= 31) || // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== "undefined" && + navigator.userAgent && + navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)) + ); + } + /** + * Colorize log arguments if enabled. + * + * @api public + */ + + function formatArgs(args) { + args[0] = + (this.useColors ? "%c" : "") + + this.namespace + + (this.useColors ? " %c" : " ") + + args[0] + + (this.useColors ? "%c " : " ") + + "+" + + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + var c = "color: " + this.color; + args.splice(1, 0, c, "color: inherit"); // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function (match) { + if (match === "%%") { + return; + } + + index++; + + if (match === "%c") { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + args.splice(lastC, 0, c); + } + /** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ + + exports.log = console.debug || console.log || function () {}; + /** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ + + function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem("debug", namespaces); + } else { + exports.storage.removeItem("debug"); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + } + /** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ + + function load() { + var r; + + try { + r = exports.storage.getItem("debug"); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + + if (!r && typeof process !== "undefined" && "env" in process) { + r = process.env.DEBUG; + } + + return r; + } + /** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + + function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + } + + module.exports = __webpack_require__( + /*! ./common */ "./node_modules/debug/src/common.js" + )(exports); + var formatters = module.exports.formatters; + /** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + + formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return "[UnexpectedJSONParseError]: " + error.message; + } + }; + + /***/ + }, + + /***/ "./node_modules/debug/src/common.js": + /*!******************************************!*\ + !*** ./node_modules/debug/src/common.js ***! + \******************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + function _toConsumableArray(arr) { + return ( + _arrayWithoutHoles(arr) || + _iterableToArray(arr) || + _unsupportedIterableToArray(arr) || + _nonIterableSpread() + ); + } + + function _nonIterableSpread() { + throw new TypeError( + "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." + ); + } + + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if ( + n === "Arguments" || + /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) + ) + return _arrayLikeToArray(o, minLen); + } + + function _iterableToArray(iter) { + if ( + typeof Symbol !== "undefined" && + Symbol.iterator in Object(iter) + ) + return Array.from(iter); + } + + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) { + arr2[i] = arr[i]; + } + return arr2; + } + + /** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + function setup(env) { + createDebug.debug = createDebug; + createDebug["default"] = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = __webpack_require__( + /*! ms */ "./node_modules/ms/index.js" + ); + createDebug.destroy = destroy; + Object.keys(env).forEach(function (key) { + createDebug[key] = env[key]; + }); + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + + createDebug.formatters = {}; + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + + function selectColor(namespace) { + var hash = 0; + + for (var i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[ + Math.abs(hash) % createDebug.colors.length + ]; + } + + createDebug.selectColor = selectColor; + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + + function createDebug(namespace) { + var prevTime; + var enableOverride = null; + + function debug() { + for ( + var _len = arguments.length, args = new Array(_len), _key = 0; + _key < _len; + _key++ + ) { + args[_key] = arguments[_key]; + } + + // Disabled? + if (!debug.enabled) { + return; + } + + var self = debug; // Set `diff` timestamp + + var curr = Number(new Date()); + var ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== "string") { + // Anything else let's inspect with %O + args.unshift("%O"); + } // Apply any `formatters` transformations + + var index = 0; + args[0] = args[0].replace( + /%([a-zA-Z%])/g, + function (match, format) { + // If we encounter an escaped % then don't increase the array index + if (match === "%%") { + return "%"; + } + + index++; + var formatter = createDebug.formatters[format]; + + if (typeof formatter === "function") { + var val = args[index]; + match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format` + + args.splice(index, 1); + index--; + } + + return match; + } + ); // Apply env-specific formatting (colors, etc.) + + createDebug.formatArgs.call(self, args); + var logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, "enabled", { + enumerable: true, + configurable: false, + get: function get() { + return enableOverride === null + ? createDebug.enabled(namespace) + : enableOverride; + }, + set: function set(v) { + enableOverride = v; + }, + }); // Env-specific initialization logic for debug instances + + if (typeof createDebug.init === "function") { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + var newDebug = createDebug( + this.namespace + + (typeof delimiter === "undefined" ? ":" : delimiter) + + namespace + ); + newDebug.log = this.log; + return newDebug; + } + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.names = []; + createDebug.skips = []; + var i; + var split = ( + typeof namespaces === "string" ? namespaces : "" + ).split(/[\s,]+/); + var len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, ".*?"); + + if (namespaces[0] === "-") { + createDebug.skips.push( + new RegExp("^" + namespaces.substr(1) + "$") + ); + } else { + createDebug.names.push(new RegExp("^" + namespaces + "$")); + } + } + } + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + + function disable() { + var namespaces = [] + .concat( + _toConsumableArray(createDebug.names.map(toNamespace)), + _toConsumableArray( + createDebug.skips + .map(toNamespace) + .map(function (namespace) { + return "-" + namespace; + }) + ) + ) + .join(","); + createDebug.enable(""); + return namespaces; + } + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + + function enabled(name) { + if (name[name.length - 1] === "*") { + return true; + } + + var i; + var len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + + function toNamespace(regexp) { + return regexp + .toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, "*"); + } + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + + return val; + } + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + + function destroy() { + console.warn( + "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`." + ); + } + + createDebug.enable(createDebug.load()); + return createDebug; + } + + module.exports = setup; + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/globalThis.browser.js": + /*!*****************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/globalThis.browser.js ***! + \*****************************************************************/ + /*! no static exports found */ + /***/ function (module, exports) { + module.exports = (function () { + if (typeof self !== "undefined") { + return self; + } else if (typeof window !== "undefined") { + return window; + } else { + return Function("return this")(); + } + })(); + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/index.js": + /*!****************************************************!*\ + !*** ./node_modules/engine.io-client/lib/index.js ***! + \****************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + var Socket = __webpack_require__( + /*! ./socket */ "./node_modules/engine.io-client/lib/socket.js" + ); + + module.exports = function (uri, opts) { + return new Socket(uri, opts); + }; + /** + * Expose deps for legacy compatibility + * and standalone browser access. + */ + + module.exports.Socket = Socket; + module.exports.protocol = Socket.protocol; // this is an int + + module.exports.Transport = __webpack_require__( + /*! ./transport */ "./node_modules/engine.io-client/lib/transport.js" + ); + module.exports.transports = __webpack_require__( + /*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js" + ); + module.exports.parser = __webpack_require__( + /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" + ); + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/socket.js": + /*!*****************************************************!*\ + !*** ./node_modules/engine.io-client/lib/socket.js ***! + \*****************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + function _extends() { + _extends = + Object.assign || + function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); + } + + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) + _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError( + "Super expression must either be null or a function" + ); + } + subClass.prototype = Object.create( + superClass && superClass.prototype, + { + constructor: { + value: subClass, + writable: true, + configurable: true, + }, + } + ); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = + Object.setPrototypeOf || + function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + + function _possibleConstructorReturn(self, call) { + if ( + call && + (_typeof(call) === "object" || typeof call === "function") + ) { + return call; + } + return _assertThisInitialized(self); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + } + return self; + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) + return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Date.prototype.toString.call( + Reflect.construct(Date, [], function () {}) + ); + return true; + } catch (e) { + return false; + } + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf + : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + var transports = __webpack_require__( + /*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js" + ); + + var Emitter = __webpack_require__( + /*! component-emitter */ "./node_modules/component-emitter/index.js" + ); + + var debug = __webpack_require__( + /*! debug */ "./node_modules/debug/src/browser.js" + )("engine.io-client:socket"); + + var parser = __webpack_require__( + /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" + ); + + var parseuri = __webpack_require__( + /*! parseuri */ "./node_modules/parseuri/index.js" + ); + + var parseqs = __webpack_require__( + /*! parseqs */ "./node_modules/parseqs/index.js" + ); + + var Socket = /*#__PURE__*/ (function (_Emitter) { + _inherits(Socket, _Emitter); + + var _super = _createSuper(Socket); + + /** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} options + * @api public + */ + function Socket(uri) { + var _this; + + var opts = + arguments.length > 1 && arguments[1] !== undefined + ? arguments[1] + : {}; + + _classCallCheck(this, Socket); + + _this = _super.call(this); + + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = null; + } + + if (uri) { + uri = parseuri(uri); + opts.hostname = uri.host; + opts.secure = + uri.protocol === "https" || uri.protocol === "wss"; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parseuri(opts.host).host; + } + + _this.secure = + null != opts.secure + ? opts.secure + : typeof location !== "undefined" && + "https:" === location.protocol; + + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = _this.secure ? "443" : "80"; + } + + _this.hostname = + opts.hostname || + (typeof location !== "undefined" + ? location.hostname + : "localhost"); + _this.port = + opts.port || + (typeof location !== "undefined" && location.port + ? location.port + : _this.secure + ? 443 + : 80); + _this.transports = opts.transports || ["polling", "websocket"]; + _this.readyState = ""; + _this.writeBuffer = []; + _this.prevBufferLen = 0; + _this.opts = _extends( + { + path: "/engine.io", + agent: false, + withCredentials: false, + upgrade: true, + jsonp: true, + timestampParam: "t", + rememberUpgrade: false, + rejectUnauthorized: true, + perMessageDeflate: { + threshold: 1024, + }, + transportOptions: {}, + }, + opts + ); + _this.opts.path = _this.opts.path.replace(/\/$/, "") + "/"; + + if (typeof _this.opts.query === "string") { + _this.opts.query = parseqs.decode(_this.opts.query); + } // set on handshake + + _this.id = null; + _this.upgrades = null; + _this.pingInterval = null; + _this.pingTimeout = null; // set on heartbeat + + _this.pingTimeoutTimer = null; + + if (typeof addEventListener === "function") { + addEventListener( + "beforeunload", + function () { + if (_this.transport) { + // silently close the transport + _this.transport.removeAllListeners(); + + _this.transport.close(); + } + }, + false + ); + } + + _this.open(); + + return _this; + } + /** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + + _createClass(Socket, [ + { + key: "createTransport", + value: function createTransport(name) { + debug('creating transport "%s"', name); + var query = clone(this.opts.query); // append engine.io protocol identifier + + query.EIO = parser.protocol; // transport name + + query.transport = name; // session id if we already have one + + if (this.id) query.sid = this.id; + + var opts = _extends( + {}, + this.opts.transportOptions[name], + this.opts, + { + query: query, + socket: this, + hostname: this.hostname, + secure: this.secure, + port: this.port, + } + ); + + debug("options: %j", opts); + return new transports[name](opts); + }, + /** + * Initializes transport to use and starts probe. + * + * @api private + */ + }, + { + key: "open", + value: function open() { + var transport; + + if ( + this.opts.rememberUpgrade && + Socket.priorWebsocketSuccess && + this.transports.indexOf("websocket") !== -1 + ) { + transport = "websocket"; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + var self = this; + setTimeout(function () { + self.emit("error", "No transports available"); + }, 0); + return; + } else { + transport = this.transports[0]; + } + + this.readyState = "opening"; // Retry with the next transport if the transport is disabled (jsonp: false) + + try { + transport = this.createTransport(transport); + } catch (e) { + debug("error while creating transport: %s", e); + this.transports.shift(); + this.open(); + return; + } + + transport.open(); + this.setTransport(transport); + }, + /** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + }, + { + key: "setTransport", + value: function setTransport(transport) { + debug("setting transport %s", transport.name); + var self = this; + + if (this.transport) { + debug( + "clearing existing transport %s", + this.transport.name + ); + this.transport.removeAllListeners(); + } // set up transport + + this.transport = transport; // set up transport listeners + + transport + .on("drain", function () { + self.onDrain(); + }) + .on("packet", function (packet) { + self.onPacket(packet); + }) + .on("error", function (e) { + self.onError(e); + }) + .on("close", function () { + self.onClose("transport close"); + }); + }, + /** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + }, + { + key: "probe", + value: function probe(name) { + debug('probing transport "%s"', name); + var transport = this.createTransport(name, { + probe: 1, + }); + var failed = false; + var self = this; + Socket.priorWebsocketSuccess = false; + + function onTransportOpen() { + if (self.onlyBinaryUpgrades) { + var upgradeLosesBinary = + !this.supportsBinary && self.transport.supportsBinary; + failed = failed || upgradeLosesBinary; + } + + if (failed) return; + debug('probe transport "%s" opened', name); + transport.send([ + { + type: "ping", + data: "probe", + }, + ]); + transport.once("packet", function (msg) { + if (failed) return; + + if ("pong" === msg.type && "probe" === msg.data) { + debug('probe transport "%s" pong', name); + self.upgrading = true; + self.emit("upgrading", transport); + if (!transport) return; + Socket.priorWebsocketSuccess = + "websocket" === transport.name; + debug( + 'pausing current transport "%s"', + self.transport.name + ); + self.transport.pause(function () { + if (failed) return; + if ("closed" === self.readyState) return; + debug( + "changing transport and sending upgrade packet" + ); + cleanup(); + self.setTransport(transport); + transport.send([ + { + type: "upgrade", + }, + ]); + self.emit("upgrade", transport); + transport = null; + self.upgrading = false; + self.flush(); + }); + } else { + debug('probe transport "%s" failed', name); + var err = new Error("probe error"); + err.transport = transport.name; + self.emit("upgradeError", err); + } + }); + } + + function freezeTransport() { + if (failed) return; // Any callback called by transport should be ignored since now + + failed = true; + cleanup(); + transport.close(); + transport = null; + } // Handle any error that happens while probing + + function onerror(err) { + var error = new Error("probe error: " + err); + error.transport = transport.name; + freezeTransport(); + debug( + 'probe transport "%s" failed because of error: %s', + name, + err + ); + self.emit("upgradeError", error); + } + + function onTransportClose() { + onerror("transport closed"); + } // When the socket is closed while we're probing + + function onclose() { + onerror("socket closed"); + } // When the socket is upgraded while we're probing + + function onupgrade(to) { + if (transport && to.name !== transport.name) { + debug( + '"%s" works - aborting "%s"', + to.name, + transport.name + ); + freezeTransport(); + } + } // Remove all listeners on the transport and on self + + function cleanup() { + transport.removeListener("open", onTransportOpen); + transport.removeListener("error", onerror); + transport.removeListener("close", onTransportClose); + self.removeListener("close", onclose); + self.removeListener("upgrading", onupgrade); + } + + transport.once("open", onTransportOpen); + transport.once("error", onerror); + transport.once("close", onTransportClose); + this.once("close", onclose); + this.once("upgrading", onupgrade); + transport.open(); + }, + /** + * Called when connection is deemed open. + * + * @api public + */ + }, + { + key: "onOpen", + value: function onOpen() { + debug("socket open"); + this.readyState = "open"; + Socket.priorWebsocketSuccess = + "websocket" === this.transport.name; + this.emit("open"); + this.flush(); // we check for `readyState` in case an `open` + // listener already closed the socket + + if ( + "open" === this.readyState && + this.opts.upgrade && + this.transport.pause + ) { + debug("starting upgrade probes"); + var i = 0; + var l = this.upgrades.length; + + for (; i < l; i++) { + this.probe(this.upgrades[i]); + } + } + }, + /** + * Handles a packet. + * + * @api private + */ + }, + { + key: "onPacket", + value: function onPacket(packet) { + if ( + "opening" === this.readyState || + "open" === this.readyState || + "closing" === this.readyState + ) { + debug( + 'socket receive: type "%s", data "%s"', + packet.type, + packet.data + ); + this.emit("packet", packet); // Socket is live - any packet counts + + this.emit("heartbeat"); + + switch (packet.type) { + case "open": + this.onHandshake(JSON.parse(packet.data)); + break; + + case "ping": + this.resetPingTimeout(); + this.sendPacket("pong"); + this.emit("pong"); + break; + + case "error": + var err = new Error("server error"); + err.code = packet.data; + this.onError(err); + break; + + case "message": + this.emit("data", packet.data); + this.emit("message", packet.data); + break; + } + } else { + debug( + 'packet received with socket readyState "%s"', + this.readyState + ); + } + }, + /** + * Called upon handshake completion. + * + * @param {Object} handshake obj + * @api private + */ + }, + { + key: "onHandshake", + value: function onHandshake(data) { + this.emit("handshake", data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.onOpen(); // In case open handler closes socket + + if ("closed" === this.readyState) return; + this.resetPingTimeout(); + }, + /** + * Sets and resets ping timeout timer based on server pings. + * + * @api private + */ + }, + { + key: "resetPingTimeout", + value: function resetPingTimeout() { + var _this2 = this; + + clearTimeout(this.pingTimeoutTimer); + this.pingTimeoutTimer = setTimeout(function () { + _this2.onClose("ping timeout"); + }, this.pingInterval + this.pingTimeout); + }, + /** + * Called on `drain` event + * + * @api private + */ + }, + { + key: "onDrain", + value: function onDrain() { + this.writeBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + + this.prevBufferLen = 0; + + if (0 === this.writeBuffer.length) { + this.emit("drain"); + } else { + this.flush(); + } + }, + /** + * Flush write buffers. + * + * @api private + */ + }, + { + key: "flush", + value: function flush() { + if ( + "closed" !== this.readyState && + this.transport.writable && + !this.upgrading && + this.writeBuffer.length + ) { + debug( + "flushing %d packets in socket", + this.writeBuffer.length + ); + this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + + this.prevBufferLen = this.writeBuffer.length; + this.emit("flush"); + } + }, + /** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + }, + { + key: "write", + value: function write(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + }, + }, + { + key: "send", + value: function send(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + }, + /** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + }, + { + key: "sendPacket", + value: function sendPacket(type, data, options, fn) { + if ("function" === typeof data) { + fn = data; + data = undefined; + } + + if ("function" === typeof options) { + fn = options; + options = null; + } + + if ( + "closing" === this.readyState || + "closed" === this.readyState + ) { + return; + } + + options = options || {}; + options.compress = false !== options.compress; + var packet = { + type: type, + data: data, + options: options, + }; + this.emit("packetCreate", packet); + this.writeBuffer.push(packet); + if (fn) this.once("flush", fn); + this.flush(); + }, + /** + * Closes the connection. + * + * @api private + */ + }, + { + key: "close", + value: function close() { + var self = this; + + if ( + "opening" === this.readyState || + "open" === this.readyState + ) { + this.readyState = "closing"; + + if (this.writeBuffer.length) { + this.once("drain", function () { + if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + + function close() { + self.onClose("forced close"); + debug("socket closing - telling transport to close"); + self.transport.close(); + } + + function cleanupAndClose() { + self.removeListener("upgrade", cleanupAndClose); + self.removeListener("upgradeError", cleanupAndClose); + close(); + } + + function waitForUpgrade() { + // wait for upgrade to finish since we can't send packets while pausing a transport + self.once("upgrade", cleanupAndClose); + self.once("upgradeError", cleanupAndClose); + } + + return this; + }, + /** + * Called upon transport error + * + * @api private + */ + }, + { + key: "onError", + value: function onError(err) { + debug("socket error %j", err); + Socket.priorWebsocketSuccess = false; + this.emit("error", err); + this.onClose("transport error", err); + }, + /** + * Called upon transport close. + * + * @api private + */ + }, + { + key: "onClose", + value: function onClose(reason, desc) { + if ( + "opening" === this.readyState || + "open" === this.readyState || + "closing" === this.readyState + ) { + debug('socket close with reason: "%s"', reason); + var self = this; // clear timers + + clearTimeout(this.pingIntervalTimer); + clearTimeout(this.pingTimeoutTimer); // stop event from firing again for transport + + this.transport.removeAllListeners("close"); // ensure transport won't stay open + + this.transport.close(); // ignore further transport communication + + this.transport.removeAllListeners(); // set ready state + + this.readyState = "closed"; // clear session id + + this.id = null; // emit close event + + this.emit("close", reason, desc); // clean buffers after, so users can still + // grab the buffers on `close` event + + self.writeBuffer = []; + self.prevBufferLen = 0; + } + }, + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + }, + { + key: "filterUpgrades", + value: function filterUpgrades(upgrades) { + var filteredUpgrades = []; + var i = 0; + var j = upgrades.length; + + for (; i < j; i++) { + if (~this.transports.indexOf(upgrades[i])) + filteredUpgrades.push(upgrades[i]); + } + + return filteredUpgrades; + }, + }, + ]); + + return Socket; + })(Emitter); + + Socket.priorWebsocketSuccess = false; + /** + * Protocol version. + * + * @api public + */ + + Socket.protocol = parser.protocol; // this is an int + + function clone(obj) { + var o = {}; + + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + o[i] = obj[i]; + } + } + + return o; + } + + module.exports = Socket; + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/transport.js": + /*!********************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transport.js ***! + \********************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) + _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError( + "Super expression must either be null or a function" + ); + } + subClass.prototype = Object.create( + superClass && superClass.prototype, + { + constructor: { + value: subClass, + writable: true, + configurable: true, + }, + } + ); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = + Object.setPrototypeOf || + function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + + function _possibleConstructorReturn(self, call) { + if ( + call && + (_typeof(call) === "object" || typeof call === "function") + ) { + return call; + } + return _assertThisInitialized(self); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + } + return self; + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) + return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Date.prototype.toString.call( + Reflect.construct(Date, [], function () {}) + ); + return true; + } catch (e) { + return false; + } + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf + : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + var parser = __webpack_require__( + /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" + ); + + var Emitter = __webpack_require__( + /*! component-emitter */ "./node_modules/component-emitter/index.js" + ); + + var Transport = /*#__PURE__*/ (function (_Emitter) { + _inherits(Transport, _Emitter); + + var _super = _createSuper(Transport); + + /** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + function Transport(opts) { + var _this; + + _classCallCheck(this, Transport); + + _this = _super.call(this); + _this.opts = opts; + _this.query = opts.query; + _this.readyState = ""; + _this.socket = opts.socket; + return _this; + } + /** + * Emits an error. + * + * @param {String} str + * @return {Transport} for chaining + * @api public + */ + + _createClass(Transport, [ + { + key: "onError", + value: function onError(msg, desc) { + var err = new Error(msg); + err.type = "TransportError"; + err.description = desc; + this.emit("error", err); + return this; + }, + /** + * Opens the transport. + * + * @api public + */ + }, + { + key: "open", + value: function open() { + if ("closed" === this.readyState || "" === this.readyState) { + this.readyState = "opening"; + this.doOpen(); + } + + return this; + }, + /** + * Closes the transport. + * + * @api private + */ + }, + { + key: "close", + value: function close() { + if ( + "opening" === this.readyState || + "open" === this.readyState + ) { + this.doClose(); + this.onClose(); + } + + return this; + }, + /** + * Sends multiple packets. + * + * @param {Array} packets + * @api private + */ + }, + { + key: "send", + value: function send(packets) { + if ("open" === this.readyState) { + this.write(packets); + } else { + throw new Error("Transport not open"); + } + }, + /** + * Called upon open + * + * @api private + */ + }, + { + key: "onOpen", + value: function onOpen() { + this.readyState = "open"; + this.writable = true; + this.emit("open"); + }, + /** + * Called with data. + * + * @param {String} data + * @api private + */ + }, + { + key: "onData", + value: function onData(data) { + var packet = parser.decodePacket( + data, + this.socket.binaryType + ); + this.onPacket(packet); + }, + /** + * Called with a decoded packet. + */ + }, + { + key: "onPacket", + value: function onPacket(packet) { + this.emit("packet", packet); + }, + /** + * Called upon close. + * + * @api private + */ + }, + { + key: "onClose", + value: function onClose() { + this.readyState = "closed"; + this.emit("close"); + }, + }, + ]); + + return Transport; + })(Emitter); + + module.exports = Transport; + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/transports/index.js": + /*!***************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transports/index.js ***! + \***************************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + var XMLHttpRequest = __webpack_require__( + /*! xmlhttprequest-ssl */ "./node_modules/engine.io-client/lib/xmlhttprequest.js" + ); + + var XHR = __webpack_require__( + /*! ./polling-xhr */ "./node_modules/engine.io-client/lib/transports/polling-xhr.js" + ); + + var JSONP = __webpack_require__( + /*! ./polling-jsonp */ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js" + ); + + var websocket = __webpack_require__( + /*! ./websocket */ "./node_modules/engine.io-client/lib/transports/websocket.js" + ); + + exports.polling = polling; + exports.websocket = websocket; + /** + * Polling transport polymorphic constructor. + * Decides on xhr vs jsonp based on feature detection. + * + * @api private + */ + + function polling(opts) { + var xhr; + var xd = false; + var xs = false; + var jsonp = false !== opts.jsonp; + + if (typeof location !== "undefined") { + var isSSL = "https:" === location.protocol; + var port = location.port; // some user agents have empty `location.port` + + if (!port) { + port = isSSL ? 443 : 80; + } + + xd = opts.hostname !== location.hostname || port !== opts.port; + xs = opts.secure !== isSSL; + } + + opts.xdomain = xd; + opts.xscheme = xs; + xhr = new XMLHttpRequest(opts); + + if ("open" in xhr && !opts.forceJSONP) { + return new XHR(opts); + } else { + if (!jsonp) throw new Error("JSONP disabled"); + return new JSONP(opts); + } + } + + /***/ + }, + + /***/ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js": + /*!***********************************************************************!*\ + !*** ./node_modules/engine.io-client/lib/transports/polling-jsonp.js ***! + \***********************************************************************/ + /*! no static exports found */ + /***/ function (module, exports, __webpack_require__) { + function _typeof(obj) { + "@babel/helpers - typeof"; + if ( + typeof Symbol === "function" && + typeof Symbol.iterator === "symbol" + ) { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && + typeof Symbol === "function" && + obj.constructor === Symbol && + obj !== Symbol.prototype + ? "symbol" + : typeof obj; + }; + } + return _typeof(obj); + } + + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) + _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + return Constructor; + } + + function _get(target, property, receiver) { + if (typeof Reflect !== "undefined" && Reflect.get) { + _get = Reflect.get; + } else { + _get = function _get(target, property, receiver) { + var base = _superPropBase(target, property); + if (!base) return; + var desc = Object.getOwnPropertyDescriptor(base, property); + if (desc.get) { + return desc.get.call(receiver); + } + return desc.value; + }; + } + return _get(target, property, receiver || target); + } + + function _superPropBase(object, property) { + while (!Object.prototype.hasOwnProperty.call(object, property)) { + object = _getPrototypeOf(object); + if (object === null) break; + } + return object; + } + + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError( + "Super expression must either be null or a function" + ); + } + subClass.prototype = Object.create( + superClass && superClass.prototype, + { + constructor: { + value: subClass, + writable: true, + configurable: true, + }, + } + ); + if (superClass) _setPrototypeOf(subClass, superClass); + } + + function _setPrototypeOf(o, p) { + _setPrototypeOf = + Object.setPrototypeOf || + function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + + function _possibleConstructorReturn(self, call) { + if ( + call && + (_typeof(call) === "object" || typeof call === "function") + ) { + return call; + } + return _assertThisInitialized(self); + } + + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError( + "this hasn't been initialised - super() hasn't been called" + ); + } + return self; + } + + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) + return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Date.prototype.toString.call( + Reflect.construct(Date, [], function () {}) + ); + return true; + } catch (e) { + return false; + } + } + + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf + ? Object.getPrototypeOf + : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + + var Polling = __webpack_require__( + /*! ./polling */ "./node_modules/engine.io-client/lib/transports/polling.js" + ); + + var globalThis = __webpack_require__( + /*! ../globalThis */ "./node_modules/engine.io-client/lib/globalThis.browser.js" + ); + + var rNewline = /\n/g; + var rEscapedNewline = /\\n/g; + /** + * Global JSONP callbacks. + */ + + var callbacks; + + var JSONPPolling = /*#__PURE__*/ (function (_Polling) { + _inherits(JSONPPolling, _Polling); + + var _super = _createSuper(JSONPPolling); + + /** + * JSONP Polling constructor. + * + * @param {Object} opts. + * @api public + */ + function JSONPPolling(opts) { + var _this; + + _classCallCheck(this, JSONPPolling); + + _this = _super.call(this, opts); + _this.query = _this.query || {}; // define global callbacks array if not present + // we do this here (lazily) to avoid unneeded global pollution + + if (!callbacks) { + // we need to consider multiple engines in the same page + callbacks = globalThis.___eio = globalThis.___eio || []; + } // callback identifier + + _this.index = callbacks.length; // add callback to jsonp global + + var self = _assertThisInitialized(_this); + + callbacks.push(function (msg) { + self.onData(msg); + }); // append to query string + + _this.query.j = _this.index; + return _this; + } + /** + * JSONP only supports binary as base64 encoded strings + */ + + _createClass(JSONPPolling, [ + { + key: "doClose", + + /** + * Closes the socket. + * + * @api private + */ + value: function doClose() { + if (this.script) { + // prevent spurious errors from being emitted when the window is unloaded + this.script.onerror = function () {}; + + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + if (this.form) { + this.form.parentNode.removeChild(this.form); + this.form = null; + this.iframe = null; + } + + _get( + _getPrototypeOf(JSONPPolling.prototype), + "doClose", + this + ).call(this); + }, + /** + * Starts a poll cycle. + * + * @api private + */ + }, + { + key: "doPoll", + value: function doPoll() { + var self = this; + var script = document.createElement("script"); + + if (this.script) { + this.script.parentNode.removeChild(this.script); + this.script = null; + } + + script.async = true; + script.src = this.uri(); + + script.onerror = function (e) { + self.onError("jsonp poll error", e); + }; + + var insertAt = document.getElementsByTagName("script")[0]; + + if (insertAt) { + insertAt.parentNode.insertBefore(script, insertAt); + } else { + (document.head || document.body).appendChild(script); + } + + this.script = script; + var isUAgecko = + "undefined" !== typeof navigator && + /gecko/i.test(navigator.userAgent); + + if (isUAgecko) { + setTimeout(function () { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + document.body.removeChild(iframe); + }, 100); + } + }, + /** + * Writes with a hidden iframe. + * + * @param {String} data to send + * @param {Function} called upon flush. + * @api private + */ + }, + { + key: "doWrite", + value: function doWrite(data, fn) { + var self = this; + var iframe; + + if (!this.form) { + var form = document.createElement("form"); + var area = document.createElement("textarea"); + var id = (this.iframeId = "eio_iframe_" + this.index); + form.className = "socketio"; + form.style.position = "absolute"; + form.style.top = "-1000px"; + form.style.left = "-1000px"; + form.target = id; + form.method = "POST"; + form.setAttribute("accept-charset", "utf-8"); + area.name = "d"; + form.appendChild(area); + document.body.appendChild(form); + this.form = form; + this.area = area; + } + + this.form.action = this.uri(); + + function complete() { + initIframe(); + fn(); + } + + function initIframe() { + if (self.iframe) { + try { + self.form.removeChild(self.iframe); + } catch (e) { + self.onError("jsonp polling iframe removal error", e); + } + } + + try { + // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) + var html = + ' \ No newline at end of file diff --git a/src/agentscope/web/studio/static/js/index.js b/src/agentscope/web/studio/static/js/index.js index 185f5aa9f..bb9899050 100644 --- a/src/agentscope/web/studio/static/js/index.js +++ b/src/agentscope/web/studio/static/js/index.js @@ -45,8 +45,10 @@ function initializeTabPageByUrl(pageUrl, firstTime) { case 'static/html/dashboard.html': initializeDashboardPage(); break; - case 'static/html/workstation.html': - initializeWorkstationPage(); + case 'static/html/workstation_iframe.html': + let script = document.createElement('script'); + script.src = 'static/js/workstation_iframe.js'; + document.head.appendChild(script); break; } } diff --git a/src/agentscope/web/studio/static/js/workstation.js b/src/agentscope/web/studio/static/js/workstation.js index 2020fc855..4db2be02a 100644 --- a/src/agentscope/web/studio/static/js/workstation.js +++ b/src/agentscope/web/studio/static/js/workstation.js @@ -36,8 +36,7 @@ async function fetchHtml(fileName) { } async function initializeWorkstationPage() { - currentZIndex = 0; - + console.log("Initialize Workstation Page") // Initialize the Drawflow editor let id = document.getElementById("drawflow"); editor = new Drawflow(id); @@ -2009,4 +2008,4 @@ function showSurveyModal() { function hideSurveyModal() { document.getElementById("surveyModal").style.display = "none"; -} +} \ No newline at end of file diff --git a/src/agentscope/web/studio/static/js/workstation_iframe.js b/src/agentscope/web/studio/static/js/workstation_iframe.js new file mode 100644 index 000000000..cc6d9209a --- /dev/null +++ b/src/agentscope/web/studio/static/js/workstation_iframe.js @@ -0,0 +1,3 @@ +var iframe = document.getElementById('workstation-iframe'); +var currentUrl = window.location.protocol + '//' + window.location.host + '/workstation'; +iframe.src = currentUrl; \ No newline at end of file diff --git a/src/agentscope/web/studio/templates/index.html b/src/agentscope/web/studio/templates/index.html index 4e75067f5..77529237b 100644 --- a/src/agentscope/web/studio/templates/index.html +++ b/src/agentscope/web/studio/templates/index.html @@ -30,30 +30,6 @@ href="{{ url_for('static', filename='css_third_party/clusterize.css') }}"> - - -{# For workstation#} - - - - - - - - - - - - - -{# TODO: remove#} - - - - - @@ -75,7 +51,7 @@ + + + \ No newline at end of file From b50c33d0c21db1cb1b094ef671d968fdd5fa5753 Mon Sep 17 00:00:00 2001 From: DavdGao Date: Mon, 27 May 2024 17:10:11 +0800 Subject: [PATCH 38/80] fix --- src/agentscope/web/studio/_app.py | 2 ++ src/agentscope/web/studio/static/css/index.css | 1 + 2 files changed, 3 insertions(+) diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index 91f0d466f..7b5b0eec8 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -515,3 +515,5 @@ def init( debug=debug, allow_unsafe_werkzeug=True, ) + +init("./instance", debug=True) \ No newline at end of file diff --git a/src/agentscope/web/studio/static/css/index.css b/src/agentscope/web/studio/static/css/index.css index 5a4788c70..cd1c3f0ff 100644 --- a/src/agentscope/web/studio/static/css/index.css +++ b/src/agentscope/web/studio/static/css/index.css @@ -37,6 +37,7 @@ body { display: flex; width: 100%; height: 100%; + border: 0; } #navigation-bar { From 16300c6c9ee434ab9ecf84f7acb03ab7ba69a5f3 Mon Sep 17 00:00:00 2001 From: DavdGao Date: Mon, 27 May 2024 17:16:18 +0800 Subject: [PATCH 39/80] push --- src/agentscope/web/studio/static/js/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/agentscope/web/studio/static/js/index.js b/src/agentscope/web/studio/static/js/index.js index bb9899050..58a2a88bd 100644 --- a/src/agentscope/web/studio/static/js/index.js +++ b/src/agentscope/web/studio/static/js/index.js @@ -40,7 +40,7 @@ function isScriptLoaded(src) { } // After loading different pages, we need to call the initialization function of this page -function initializeTabPageByUrl(pageUrl, firstTime) { +function initializeTabPageByUrl(pageUrl) { switch (pageUrl) { case 'static/html/dashboard.html': initializeDashboardPage(); @@ -84,13 +84,13 @@ function loadTabPage(pageUrl, javascriptUrl) { script.src = javascriptUrl; script.onload = function () { // The first time we must initialize the page within the onload function to ensure the script is loaded - initializeTabPageByUrl(pageUrl, true); + initializeTabPageByUrl(pageUrl); } document.head.appendChild(script); } else { console.log("Script already loaded for " + javascriptUrl); // If is not the first time, we can directly call the initialization function - initializeTabPageByUrl(pageUrl, false); + initializeTabPageByUrl(pageUrl); } // Load the page content From 4b89ffd74d7789fd11cf9db8ca2ddfe3a973aba5 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Tue, 28 May 2024 15:02:56 +0800 Subject: [PATCH 40/80] render dialogue content as markdown --- src/agentscope/web/studio/_app.py | 42 +- .../static/js/dashboard-detail-dialogue.js | 6 +- .../web/studio/static/js/socket.io.js | 7918 ----------------- .../static/js_third_party/marked.min.js | 6 + .../web/studio/templates/index.html | 1 + 5 files changed, 32 insertions(+), 7941 deletions(-) delete mode 100644 src/agentscope/web/studio/static/js/socket.io.js create mode 100644 src/agentscope/web/studio/static/js_third_party/marked.min.js diff --git a/src/agentscope/web/studio/_app.py b/src/agentscope/web/studio/_app.py index 854fd313d..e27dbd793 100644 --- a/src/agentscope/web/studio/_app.py +++ b/src/agentscope/web/studio/_app.py @@ -111,8 +111,8 @@ def remove_file_paths(error_trace: str) -> str: def convert_to_py( # type: ignore[no-untyped-def] - content: str, - **kwargs, + content: str, + **kwargs, ) -> Tuple: """ Convert json config to python code. @@ -322,9 +322,9 @@ def convert_config_to_py_and_run() -> Response: if status == "True": try: with tempfile.NamedTemporaryFile( - delete=False, - suffix=".py", - mode="w+t", + delete=False, + suffix=".py", + mode="w+t", ) as tmp: tmp.write(py_code) tmp.flush() @@ -351,24 +351,24 @@ def read_examples() -> Response: file_index = 1 if not os.path.exists( - os.path.join( - app.root_path, - "static", - "workstation_templates", - f"{lang}{file_index}.json", - ), + os.path.join( + app.root_path, + "static", + "workstation_templates", + f"{lang}{file_index}.json", + ), ): lang = "en" with open( - os.path.join( - app.root_path, - "static", - "workstation_templates", - f"{lang}{file_index}.json", - ), - "r", - encoding="utf-8", + os.path.join( + app.root_path, + "static", + "workstation_templates", + f"{lang}{file_index}.json", + ), + "r", + encoding="utf-8", ) as jf: data = json.load(jf) return jsonify(json=data) @@ -520,4 +520,6 @@ def init( allow_unsafe_werkzeug=True, ) -init("./instance", debug=True) \ No newline at end of file + +if __name__ == "__main__": + init("./instance", debug=True) diff --git a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js index 82d8741b4..a6d1f1a3a 100644 --- a/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js +++ b/src/agentscope/web/studio/static/js/dashboard-detail-dialogue.js @@ -112,7 +112,7 @@ function _addUserChatRow(index, pMsg) { // template.querySelector('.chat-icon'). template.querySelector(".chat-name").textContent = pMsg.name; let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += marked.parse(pMsg.content); chatBubble.innerHTML += _renderMultiModalData(pMsg.url); template.querySelector(".chat-row").setAttribute("data-index", index); return template.firstElementChild.outerHTML; @@ -124,7 +124,7 @@ function _addAssistantChatRow(index, pMsg) { template.querySelector(".chat-name").textContent = pMsg.name; let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += marked.parse(pMsg.content); chatBubble.innerHTML += _renderMultiModalData(pMsg.url); template.querySelector(".chat-row").setAttribute("data-index", index); return template.firstElementChild.outerHTML; @@ -134,7 +134,7 @@ function _addSystemChatRow(index, pMsg) { const template = chatRowSystemTemplate.cloneNode(true); template.querySelector(".chat-name").textContent = pMsg.name; let chatBubble = template.querySelector(".chat-bubble"); - chatBubble.textContent = pMsg.content; + chatBubble.innerHTML += marked.parse(pMsg.content); chatBubble.innerHTML += _renderMultiModalData(pMsg.url); template.querySelector(".chat-row").setAttribute("data-index", index); return template.firstElementChild.outerHTML; diff --git a/src/agentscope/web/studio/static/js/socket.io.js b/src/agentscope/web/studio/static/js/socket.io.js deleted file mode 100644 index b28d6bb1f..000000000 --- a/src/agentscope/web/studio/static/js/socket.io.js +++ /dev/null @@ -1,7918 +0,0 @@ -/*! - * Socket.IO v3.1.3 - * (c) 2014-2021 Guillermo Rauch - * Released under the MIT License. - */ -(function webpackUniversalModuleDefinition(root, factory) { - if (typeof exports === "object" && typeof module === "object") - module.exports = factory(); - else if (typeof define === "function" && define.amd) define([], factory); - else if (typeof exports === "object") exports["io"] = factory(); - else root["io"] = factory(); -})(self, function () { - return /******/ (function (modules) { - // webpackBootstrap - /******/ // The module cache - /******/ var installedModules = {}; - /******/ - /******/ // The require function - /******/ function __webpack_require__(moduleId) { - /******/ - /******/ // Check if module is in cache - /******/ if (installedModules[moduleId]) { - /******/ return installedModules[moduleId].exports; - /******/ - } - /******/ // Create a new module (and put it into the cache) - /******/ var module = (installedModules[moduleId] = { - /******/ i: moduleId, - /******/ l: false, - /******/ exports: {}, - /******/ - }); - /******/ - /******/ // Execute the module function - /******/ modules[moduleId].call( - module.exports, - module, - module.exports, - __webpack_require__ - ); - /******/ - /******/ // Flag the module as loaded - /******/ module.l = true; - /******/ - /******/ // Return the exports of the module - /******/ return module.exports; - /******/ - } - /******/ - /******/ - /******/ // expose the modules object (__webpack_modules__) - /******/ __webpack_require__.m = modules; - /******/ - /******/ // expose the module cache - /******/ __webpack_require__.c = installedModules; - /******/ - /******/ // define getter function for harmony exports - /******/ __webpack_require__.d = function (exports, name, getter) { - /******/ if (!__webpack_require__.o(exports, name)) { - /******/ Object.defineProperty(exports, name, { - enumerable: true, - get: getter, - }); - /******/ - } - /******/ - }; - /******/ - /******/ // define __esModule on exports - /******/ __webpack_require__.r = function (exports) { - /******/ if (typeof Symbol !== "undefined" && Symbol.toStringTag) { - /******/ Object.defineProperty(exports, Symbol.toStringTag, { - value: "Module", - }); - /******/ - } - /******/ Object.defineProperty(exports, "__esModule", { value: true }); - /******/ - }; - /******/ - /******/ // create a fake namespace object - /******/ // mode & 1: value is a module id, require it - /******/ // mode & 2: merge all properties of value into the ns - /******/ // mode & 4: return value when already ns object - /******/ // mode & 8|1: behave like require - /******/ __webpack_require__.t = function (value, mode) { - /******/ if (mode & 1) value = __webpack_require__(value); - /******/ if (mode & 8) return value; - /******/ if ( - mode & 4 && - typeof value === "object" && - value && - value.__esModule - ) - return value; - /******/ var ns = Object.create(null); - /******/ __webpack_require__.r(ns); - /******/ Object.defineProperty(ns, "default", { - enumerable: true, - value: value, - }); - /******/ if (mode & 2 && typeof value != "string") - for (var key in value) - __webpack_require__.d( - ns, - key, - function (key) { - return value[key]; - }.bind(null, key) - ); - /******/ return ns; - /******/ - }; - /******/ - /******/ // getDefaultExport function for compatibility with non-harmony modules - /******/ __webpack_require__.n = function (module) { - /******/ var getter = - module && module.__esModule - ? /******/ function getDefault() { - return module["default"]; - } - : /******/ function getModuleExports() { - return module; - }; - /******/ __webpack_require__.d(getter, "a", getter); - /******/ return getter; - /******/ - }; - /******/ - /******/ // Object.prototype.hasOwnProperty.call - /******/ __webpack_require__.o = function (object, property) { - return Object.prototype.hasOwnProperty.call(object, property); - }; - /******/ - /******/ // __webpack_public_path__ - /******/ __webpack_require__.p = ""; - /******/ - /******/ - /******/ // Load entry module and return exports - /******/ return __webpack_require__( - (__webpack_require__.s = "./build/index.js") - ); - /******/ - })( - /************************************************************************/ - /******/ { - /***/ "./build/index.js": - /*!************************!*\ - !*** ./build/index.js ***! - \************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - "use strict"; - - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - Object.defineProperty(exports, "__esModule", { - value: true, - }); - exports.Socket = - exports.io = - exports.Manager = - exports.protocol = - void 0; - - var url_1 = __webpack_require__(/*! ./url */ "./build/url.js"); - - var manager_1 = __webpack_require__( - /*! ./manager */ "./build/manager.js" - ); - - var socket_1 = __webpack_require__( - /*! ./socket */ "./build/socket.js" - ); - - Object.defineProperty(exports, "Socket", { - enumerable: true, - get: function get() { - return socket_1.Socket; - }, - }); - - var debug = __webpack_require__( - /*! debug */ "./node_modules/debug/src/browser.js" - )("socket.io-client"); - /** - * Module exports. - */ - - module.exports = exports = lookup; - /** - * Managers cache. - */ - - var cache = (exports.managers = {}); - - function lookup(uri, opts) { - if (_typeof(uri) === "object") { - opts = uri; - uri = undefined; - } - - opts = opts || {}; - var parsed = url_1.url(uri, opts.path); - var source = parsed.source; - var id = parsed.id; - var path = parsed.path; - var sameNamespace = cache[id] && path in cache[id]["nsps"]; - var newConnection = - opts.forceNew || - opts["force new connection"] || - false === opts.multiplex || - sameNamespace; - var io; - - if (newConnection) { - debug("ignoring socket cache for %s", source); - io = new manager_1.Manager(source, opts); - } else { - if (!cache[id]) { - debug("new io instance for %s", source); - cache[id] = new manager_1.Manager(source, opts); - } - - io = cache[id]; - } - - if (parsed.query && !opts.query) { - opts.query = parsed.queryKey; - } - - return io.socket(parsed.path, opts); - } - - exports.io = lookup; - /** - * Protocol version. - * - * @public - */ - - var socket_io_parser_1 = __webpack_require__( - /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" - ); - - Object.defineProperty(exports, "protocol", { - enumerable: true, - get: function get() { - return socket_io_parser_1.protocol; - }, - }); - /** - * `connect`. - * - * @param {String} uri - * @public - */ - - exports.connect = lookup; - /** - * Expose constructors for standalone build. - * - * @public - */ - - var manager_2 = __webpack_require__( - /*! ./manager */ "./build/manager.js" - ); - - Object.defineProperty(exports, "Manager", { - enumerable: true, - get: function get() { - return manager_2.Manager; - }, - }); - - /***/ - }, - - /***/ "./build/manager.js": - /*!**************************!*\ - !*** ./build/manager.js ***! - \**************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - "use strict"; - - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) - _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _get(target, property, receiver) { - if (typeof Reflect !== "undefined" && Reflect.get) { - _get = Reflect.get; - } else { - _get = function _get(target, property, receiver) { - var base = _superPropBase(target, property); - if (!base) return; - var desc = Object.getOwnPropertyDescriptor(base, property); - if (desc.get) { - return desc.get.call(receiver); - } - return desc.value; - }; - } - return _get(target, property, receiver || target); - } - - function _superPropBase(object, property) { - while (!Object.prototype.hasOwnProperty.call(object, property)) { - object = _getPrototypeOf(object); - if (object === null) break; - } - return object; - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError( - "Super expression must either be null or a function" - ); - } - subClass.prototype = Object.create( - superClass && superClass.prototype, - { - constructor: { - value: subClass, - writable: true, - configurable: true, - }, - } - ); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _possibleConstructorReturn(self, call) { - if ( - call && - (_typeof(call) === "object" || typeof call === "function") - ) { - return call; - } - return _assertThisInitialized(self); - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - } - return self; - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) - return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Date.prototype.toString.call( - Reflect.construct(Date, [], function () {}) - ); - return true; - } catch (e) { - return false; - } - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - Object.defineProperty(exports, "__esModule", { - value: true, - }); - exports.Manager = void 0; - - var eio = __webpack_require__( - /*! engine.io-client */ "./node_modules/engine.io-client/lib/index.js" - ); - - var socket_1 = __webpack_require__( - /*! ./socket */ "./build/socket.js" - ); - - var Emitter = __webpack_require__( - /*! component-emitter */ "./node_modules/component-emitter/index.js" - ); - - var parser = __webpack_require__( - /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" - ); - - var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); - - var Backoff = __webpack_require__( - /*! backo2 */ "./node_modules/backo2/index.js" - ); - - var debug = __webpack_require__( - /*! debug */ "./node_modules/debug/src/browser.js" - )("socket.io-client:manager"); - - var Manager = /*#__PURE__*/ (function (_Emitter) { - _inherits(Manager, _Emitter); - - var _super = _createSuper(Manager); - - function Manager(uri, opts) { - var _this; - - _classCallCheck(this, Manager); - - _this = _super.call(this); - _this.nsps = {}; - _this.subs = []; - - if (uri && "object" === _typeof(uri)) { - opts = uri; - uri = undefined; - } - - opts = opts || {}; - opts.path = opts.path || "/socket.io"; - _this.opts = opts; - - _this.reconnection(opts.reconnection !== false); - - _this.reconnectionAttempts(opts.reconnectionAttempts || Infinity); - - _this.reconnectionDelay(opts.reconnectionDelay || 1000); - - _this.reconnectionDelayMax(opts.reconnectionDelayMax || 5000); - - _this.randomizationFactor(opts.randomizationFactor || 0.5); - - _this.backoff = new Backoff({ - min: _this.reconnectionDelay(), - max: _this.reconnectionDelayMax(), - jitter: _this.randomizationFactor(), - }); - - _this.timeout(null == opts.timeout ? 20000 : opts.timeout); - - _this._readyState = "closed"; - _this.uri = uri; - - var _parser = opts.parser || parser; - - _this.encoder = new _parser.Encoder(); - _this.decoder = new _parser.Decoder(); - _this._autoConnect = opts.autoConnect !== false; - if (_this._autoConnect) _this.open(); - return _this; - } - - _createClass(Manager, [ - { - key: "reconnection", - value: function reconnection(v) { - if (!arguments.length) return this._reconnection; - this._reconnection = !!v; - return this; - }, - }, - { - key: "reconnectionAttempts", - value: function reconnectionAttempts(v) { - if (v === undefined) return this._reconnectionAttempts; - this._reconnectionAttempts = v; - return this; - }, - }, - { - key: "reconnectionDelay", - value: function reconnectionDelay(v) { - var _a; - - if (v === undefined) return this._reconnectionDelay; - this._reconnectionDelay = v; - (_a = this.backoff) === null || _a === void 0 - ? void 0 - : _a.setMin(v); - return this; - }, - }, - { - key: "randomizationFactor", - value: function randomizationFactor(v) { - var _a; - - if (v === undefined) return this._randomizationFactor; - this._randomizationFactor = v; - (_a = this.backoff) === null || _a === void 0 - ? void 0 - : _a.setJitter(v); - return this; - }, - }, - { - key: "reconnectionDelayMax", - value: function reconnectionDelayMax(v) { - var _a; - - if (v === undefined) return this._reconnectionDelayMax; - this._reconnectionDelayMax = v; - (_a = this.backoff) === null || _a === void 0 - ? void 0 - : _a.setMax(v); - return this; - }, - }, - { - key: "timeout", - value: function timeout(v) { - if (!arguments.length) return this._timeout; - this._timeout = v; - return this; - }, - /** - * Starts trying to reconnect if reconnection is enabled and we have not - * started reconnecting yet - * - * @private - */ - }, - { - key: "maybeReconnectOnOpen", - value: function maybeReconnectOnOpen() { - // Only try to reconnect if it's the first time we're connecting - if ( - !this._reconnecting && - this._reconnection && - this.backoff.attempts === 0 - ) { - // keeps reconnection from firing twice for the same reconnection loop - this.reconnect(); - } - }, - /** - * Sets the current transport `socket`. - * - * @param {Function} fn - optional, callback - * @return self - * @public - */ - }, - { - key: "open", - value: function open(fn) { - var _this2 = this; - - debug("readyState %s", this._readyState); - if (~this._readyState.indexOf("open")) return this; - debug("opening %s", this.uri); - this.engine = eio(this.uri, this.opts); - var socket = this.engine; - var self = this; - this._readyState = "opening"; - this.skipReconnect = false; // emit `open` - - var openSubDestroy = on_1.on(socket, "open", function () { - self.onopen(); - fn && fn(); - }); // emit `error` - - var errorSub = on_1.on(socket, "error", function (err) { - debug("error"); - self.cleanup(); - self._readyState = "closed"; - - _get( - _getPrototypeOf(Manager.prototype), - "emit", - _this2 - ).call(_this2, "error", err); - - if (fn) { - fn(err); - } else { - // Only do this if there is no fn to handle the error - self.maybeReconnectOnOpen(); - } - }); - - if (false !== this._timeout) { - var timeout = this._timeout; - debug("connect attempt will timeout after %d", timeout); - - if (timeout === 0) { - openSubDestroy(); // prevents a race condition with the 'open' event - } // set timer - - var timer = setTimeout(function () { - debug("connect attempt timed out after %d", timeout); - openSubDestroy(); - socket.close(); - socket.emit("error", new Error("timeout")); - }, timeout); - this.subs.push(function subDestroy() { - clearTimeout(timer); - }); - } - - this.subs.push(openSubDestroy); - this.subs.push(errorSub); - return this; - }, - /** - * Alias for open() - * - * @return self - * @public - */ - }, - { - key: "connect", - value: function connect(fn) { - return this.open(fn); - }, - /** - * Called upon transport open. - * - * @private - */ - }, - { - key: "onopen", - value: function onopen() { - debug("open"); // clear old subs - - this.cleanup(); // mark as open - - this._readyState = "open"; - - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "open" - ); // add new subs - - var socket = this.engine; - this.subs.push( - on_1.on(socket, "ping", this.onping.bind(this)), - on_1.on(socket, "data", this.ondata.bind(this)), - on_1.on(socket, "error", this.onerror.bind(this)), - on_1.on(socket, "close", this.onclose.bind(this)), - on_1.on(this.decoder, "decoded", this.ondecoded.bind(this)) - ); - }, - /** - * Called upon a ping. - * - * @private - */ - }, - { - key: "onping", - value: function onping() { - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "ping" - ); - }, - /** - * Called with data. - * - * @private - */ - }, - { - key: "ondata", - value: function ondata(data) { - this.decoder.add(data); - }, - /** - * Called when parser fully decodes a packet. - * - * @private - */ - }, - { - key: "ondecoded", - value: function ondecoded(packet) { - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "packet", - packet - ); - }, - /** - * Called upon socket error. - * - * @private - */ - }, - { - key: "onerror", - value: function onerror(err) { - debug("error", err); - - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "error", - err - ); - }, - /** - * Creates a new socket for the given `nsp`. - * - * @return {Socket} - * @public - */ - }, - { - key: "socket", - value: function socket(nsp, opts) { - var socket = this.nsps[nsp]; - - if (!socket) { - socket = new socket_1.Socket(this, nsp, opts); - this.nsps[nsp] = socket; - } - - return socket; - }, - /** - * Called upon a socket close. - * - * @param socket - * @private - */ - }, - { - key: "_destroy", - value: function _destroy(socket) { - var nsps = Object.keys(this.nsps); - - for (var _i = 0, _nsps = nsps; _i < _nsps.length; _i++) { - var nsp = _nsps[_i]; - var _socket = this.nsps[nsp]; - - if (_socket.active) { - debug("socket %s is still active, skipping close", nsp); - return; - } - } - - this._close(); - }, - /** - * Writes a packet. - * - * @param packet - * @private - */ - }, - { - key: "_packet", - value: function _packet(packet) { - debug("writing packet %j", packet); - var encodedPackets = this.encoder.encode(packet); - - for (var i = 0; i < encodedPackets.length; i++) { - this.engine.write(encodedPackets[i], packet.options); - } - }, - /** - * Clean up transport subscriptions and packet buffer. - * - * @private - */ - }, - { - key: "cleanup", - value: function cleanup() { - debug("cleanup"); - this.subs.forEach(function (subDestroy) { - return subDestroy(); - }); - this.subs.length = 0; - this.decoder.destroy(); - }, - /** - * Close the current socket. - * - * @private - */ - }, - { - key: "_close", - value: function _close() { - debug("disconnect"); - this.skipReconnect = true; - this._reconnecting = false; - - if ("opening" === this._readyState) { - // `onclose` will not fire because - // an open event never happened - this.cleanup(); - } - - this.backoff.reset(); - this._readyState = "closed"; - if (this.engine) this.engine.close(); - }, - /** - * Alias for close() - * - * @private - */ - }, - { - key: "disconnect", - value: function disconnect() { - return this._close(); - }, - /** - * Called upon engine close. - * - * @private - */ - }, - { - key: "onclose", - value: function onclose(reason) { - debug("onclose"); - this.cleanup(); - this.backoff.reset(); - this._readyState = "closed"; - - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "close", - reason - ); - - if (this._reconnection && !this.skipReconnect) { - this.reconnect(); - } - }, - /** - * Attempt a reconnection. - * - * @private - */ - }, - { - key: "reconnect", - value: function reconnect() { - var _this3 = this; - - if (this._reconnecting || this.skipReconnect) return this; - var self = this; - - if (this.backoff.attempts >= this._reconnectionAttempts) { - debug("reconnect failed"); - this.backoff.reset(); - - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "reconnect_failed" - ); - - this._reconnecting = false; - } else { - var delay = this.backoff.duration(); - debug("will wait %dms before reconnect attempt", delay); - this._reconnecting = true; - var timer = setTimeout(function () { - if (self.skipReconnect) return; - debug("attempting reconnect"); - - _get( - _getPrototypeOf(Manager.prototype), - "emit", - _this3 - ).call( - _this3, - "reconnect_attempt", - self.backoff.attempts - ); // check again for the case socket closed in above events - - if (self.skipReconnect) return; - self.open(function (err) { - if (err) { - debug("reconnect attempt error"); - self._reconnecting = false; - self.reconnect(); - - _get( - _getPrototypeOf(Manager.prototype), - "emit", - _this3 - ).call(_this3, "reconnect_error", err); - } else { - debug("reconnect success"); - self.onreconnect(); - } - }); - }, delay); - this.subs.push(function subDestroy() { - clearTimeout(timer); - }); - } - }, - /** - * Called upon successful reconnect. - * - * @private - */ - }, - { - key: "onreconnect", - value: function onreconnect() { - var attempt = this.backoff.attempts; - this._reconnecting = false; - this.backoff.reset(); - - _get(_getPrototypeOf(Manager.prototype), "emit", this).call( - this, - "reconnect", - attempt - ); - }, - }, - ]); - - return Manager; - })(Emitter); - - exports.Manager = Manager; - - /***/ - }, - - /***/ "./build/on.js": - /*!*********************!*\ - !*** ./build/on.js ***! - \*********************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true, - }); - exports.on = void 0; - - function on(obj, ev, fn) { - obj.on(ev, fn); - return function subDestroy() { - obj.off(ev, fn); - }; - } - - exports.on = on; - - /***/ - }, - - /***/ "./build/socket.js": - /*!*************************!*\ - !*** ./build/socket.js ***! - \*************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - "use strict"; - - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - function _createForOfIteratorHelper(o, allowArrayLike) { - var it; - if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { - if ( - Array.isArray(o) || - (it = _unsupportedIterableToArray(o)) || - (allowArrayLike && o && typeof o.length === "number") - ) { - if (it) o = it; - var i = 0; - var F = function F() {}; - return { - s: F, - n: function n() { - if (i >= o.length) return { done: true }; - return { done: false, value: o[i++] }; - }, - e: function e(_e) { - throw _e; - }, - f: F, - }; - } - throw new TypeError( - "Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - var normalCompletion = true, - didErr = false, - err; - return { - s: function s() { - it = o[Symbol.iterator](); - }, - n: function n() { - var step = it.next(); - normalCompletion = step.done; - return step; - }, - e: function e(_e2) { - didErr = true; - err = _e2; - }, - f: function f() { - try { - if (!normalCompletion && it["return"] != null) it["return"](); - } finally { - if (didErr) throw err; - } - }, - }; - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if ( - n === "Arguments" || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ) - return _arrayLikeToArray(o, minLen); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - return arr2; - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) - _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _get(target, property, receiver) { - if (typeof Reflect !== "undefined" && Reflect.get) { - _get = Reflect.get; - } else { - _get = function _get(target, property, receiver) { - var base = _superPropBase(target, property); - if (!base) return; - var desc = Object.getOwnPropertyDescriptor(base, property); - if (desc.get) { - return desc.get.call(receiver); - } - return desc.value; - }; - } - return _get(target, property, receiver || target); - } - - function _superPropBase(object, property) { - while (!Object.prototype.hasOwnProperty.call(object, property)) { - object = _getPrototypeOf(object); - if (object === null) break; - } - return object; - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError( - "Super expression must either be null or a function" - ); - } - subClass.prototype = Object.create( - superClass && superClass.prototype, - { - constructor: { - value: subClass, - writable: true, - configurable: true, - }, - } - ); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _possibleConstructorReturn(self, call) { - if ( - call && - (_typeof(call) === "object" || typeof call === "function") - ) { - return call; - } - return _assertThisInitialized(self); - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - } - return self; - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) - return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Date.prototype.toString.call( - Reflect.construct(Date, [], function () {}) - ); - return true; - } catch (e) { - return false; - } - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - Object.defineProperty(exports, "__esModule", { - value: true, - }); - exports.Socket = void 0; - - var socket_io_parser_1 = __webpack_require__( - /*! socket.io-parser */ "./node_modules/socket.io-parser/dist/index.js" - ); - - var Emitter = __webpack_require__( - /*! component-emitter */ "./node_modules/component-emitter/index.js" - ); - - var on_1 = __webpack_require__(/*! ./on */ "./build/on.js"); - - var debug = __webpack_require__( - /*! debug */ "./node_modules/debug/src/browser.js" - )("socket.io-client:socket"); - /** - * Internal events. - * These events can't be emitted by the user. - */ - - var RESERVED_EVENTS = Object.freeze({ - connect: 1, - connect_error: 1, - disconnect: 1, - disconnecting: 1, - // EventEmitter reserved events: https://nodejs.org/api/events.html#events_event_newlistener - newListener: 1, - removeListener: 1, - }); - - var Socket = /*#__PURE__*/ (function (_Emitter) { - _inherits(Socket, _Emitter); - - var _super = _createSuper(Socket); - - /** - * `Socket` constructor. - * - * @public - */ - function Socket(io, nsp, opts) { - var _this; - - _classCallCheck(this, Socket); - - _this = _super.call(this); - _this.receiveBuffer = []; - _this.sendBuffer = []; - _this.ids = 0; - _this.acks = {}; - _this.flags = {}; - _this.io = io; - _this.nsp = nsp; - _this.ids = 0; - _this.acks = {}; - _this.receiveBuffer = []; - _this.sendBuffer = []; - _this.connected = false; - _this.disconnected = true; - _this.flags = {}; - - if (opts && opts.auth) { - _this.auth = opts.auth; - } - - if (_this.io._autoConnect) _this.open(); - return _this; - } - /** - * Subscribe to open, close and packet events - * - * @private - */ - - _createClass(Socket, [ - { - key: "subEvents", - value: function subEvents() { - if (this.subs) return; - var io = this.io; - this.subs = [ - on_1.on(io, "open", this.onopen.bind(this)), - on_1.on(io, "packet", this.onpacket.bind(this)), - on_1.on(io, "error", this.onerror.bind(this)), - on_1.on(io, "close", this.onclose.bind(this)), - ]; - }, - /** - * Whether the Socket will try to reconnect when its Manager connects or reconnects - */ - }, - { - key: "connect", - - /** - * "Opens" the socket. - * - * @public - */ - value: function connect() { - if (this.connected) return this; - this.subEvents(); - if (!this.io["_reconnecting"]) this.io.open(); // ensure open - - if ("open" === this.io._readyState) this.onopen(); - return this; - }, - /** - * Alias for connect() - */ - }, - { - key: "open", - value: function open() { - return this.connect(); - }, - /** - * Sends a `message` event. - * - * @return self - * @public - */ - }, - { - key: "send", - value: function send() { - for ( - var _len = arguments.length, - args = new Array(_len), - _key = 0; - _key < _len; - _key++ - ) { - args[_key] = arguments[_key]; - } - - args.unshift("message"); - this.emit.apply(this, args); - return this; - }, - /** - * Override `emit`. - * If the event is in `events`, it's emitted normally. - * - * @param ev - event name - * @return self - * @public - */ - }, - { - key: "emit", - value: function emit(ev) { - if (RESERVED_EVENTS.hasOwnProperty(ev)) { - throw new Error('"' + ev + '" is a reserved event name'); - } - - for ( - var _len2 = arguments.length, - args = new Array(_len2 > 1 ? _len2 - 1 : 0), - _key2 = 1; - _key2 < _len2; - _key2++ - ) { - args[_key2 - 1] = arguments[_key2]; - } - - args.unshift(ev); - var packet = { - type: socket_io_parser_1.PacketType.EVENT, - data: args, - }; - packet.options = {}; - packet.options.compress = this.flags.compress !== false; // event ack callback - - if ("function" === typeof args[args.length - 1]) { - debug("emitting packet with ack id %d", this.ids); - this.acks[this.ids] = args.pop(); - packet.id = this.ids++; - } - - var isTransportWritable = - this.io.engine && - this.io.engine.transport && - this.io.engine.transport.writable; - var discardPacket = - this.flags["volatile"] && - (!isTransportWritable || !this.connected); - - if (discardPacket) { - debug( - "discard packet as the transport is not currently writable" - ); - } else if (this.connected) { - this.packet(packet); - } else { - this.sendBuffer.push(packet); - } - - this.flags = {}; - return this; - }, - /** - * Sends a packet. - * - * @param packet - * @private - */ - }, - { - key: "packet", - value: function packet(_packet) { - _packet.nsp = this.nsp; - - this.io._packet(_packet); - }, - /** - * Called upon engine `open`. - * - * @private - */ - }, - { - key: "onopen", - value: function onopen() { - var _this2 = this; - - debug("transport is open - connecting"); - - if (typeof this.auth == "function") { - this.auth(function (data) { - _this2.packet({ - type: socket_io_parser_1.PacketType.CONNECT, - data: data, - }); - }); - } else { - this.packet({ - type: socket_io_parser_1.PacketType.CONNECT, - data: this.auth, - }); - } - }, - /** - * Called upon engine or manager `error`. - * - * @param err - * @private - */ - }, - { - key: "onerror", - value: function onerror(err) { - if (!this.connected) { - _get(_getPrototypeOf(Socket.prototype), "emit", this).call( - this, - "connect_error", - err - ); - } - }, - /** - * Called upon engine `close`. - * - * @param reason - * @private - */ - }, - { - key: "onclose", - value: function onclose(reason) { - debug("close (%s)", reason); - this.connected = false; - this.disconnected = true; - delete this.id; - - _get(_getPrototypeOf(Socket.prototype), "emit", this).call( - this, - "disconnect", - reason - ); - }, - /** - * Called with socket packet. - * - * @param packet - * @private - */ - }, - { - key: "onpacket", - value: function onpacket(packet) { - var sameNamespace = packet.nsp === this.nsp; - if (!sameNamespace) return; - - switch (packet.type) { - case socket_io_parser_1.PacketType.CONNECT: - if (packet.data && packet.data.sid) { - var id = packet.data.sid; - this.onconnect(id); - } else { - _get( - _getPrototypeOf(Socket.prototype), - "emit", - this - ).call( - this, - "connect_error", - new Error( - "It seems you are trying to reach a Socket.IO server in v2.x with a v3.x client, but they are not compatible (more information here: https://socket.io/docs/v3/migrating-from-2-x-to-3-0/)" - ) - ); - } - - break; - - case socket_io_parser_1.PacketType.EVENT: - this.onevent(packet); - break; - - case socket_io_parser_1.PacketType.BINARY_EVENT: - this.onevent(packet); - break; - - case socket_io_parser_1.PacketType.ACK: - this.onack(packet); - break; - - case socket_io_parser_1.PacketType.BINARY_ACK: - this.onack(packet); - break; - - case socket_io_parser_1.PacketType.DISCONNECT: - this.ondisconnect(); - break; - - case socket_io_parser_1.PacketType.CONNECT_ERROR: - var err = new Error(packet.data.message); // @ts-ignore - - err.data = packet.data.data; - - _get( - _getPrototypeOf(Socket.prototype), - "emit", - this - ).call(this, "connect_error", err); - - break; - } - }, - /** - * Called upon a server event. - * - * @param packet - * @private - */ - }, - { - key: "onevent", - value: function onevent(packet) { - var args = packet.data || []; - debug("emitting event %j", args); - - if (null != packet.id) { - debug("attaching ack callback to event"); - args.push(this.ack(packet.id)); - } - - if (this.connected) { - this.emitEvent(args); - } else { - this.receiveBuffer.push(Object.freeze(args)); - } - }, - }, - { - key: "emitEvent", - value: function emitEvent(args) { - if (this._anyListeners && this._anyListeners.length) { - var listeners = this._anyListeners.slice(); - - var _iterator = _createForOfIteratorHelper(listeners), - _step; - - try { - for (_iterator.s(); !(_step = _iterator.n()).done; ) { - var listener = _step.value; - listener.apply(this, args); - } - } catch (err) { - _iterator.e(err); - } finally { - _iterator.f(); - } - } - - _get(_getPrototypeOf(Socket.prototype), "emit", this).apply( - this, - args - ); - }, - /** - * Produces an ack callback to emit with an event. - * - * @private - */ - }, - { - key: "ack", - value: function ack(id) { - var self = this; - var sent = false; - return function () { - // prevent double callbacks - if (sent) return; - sent = true; - - for ( - var _len3 = arguments.length, - args = new Array(_len3), - _key3 = 0; - _key3 < _len3; - _key3++ - ) { - args[_key3] = arguments[_key3]; - } - - debug("sending ack %j", args); - self.packet({ - type: socket_io_parser_1.PacketType.ACK, - id: id, - data: args, - }); - }; - }, - /** - * Called upon a server acknowlegement. - * - * @param packet - * @private - */ - }, - { - key: "onack", - value: function onack(packet) { - var ack = this.acks[packet.id]; - - if ("function" === typeof ack) { - debug("calling ack %s with %j", packet.id, packet.data); - ack.apply(this, packet.data); - delete this.acks[packet.id]; - } else { - debug("bad ack %s", packet.id); - } - }, - /** - * Called upon server connect. - * - * @private - */ - }, - { - key: "onconnect", - value: function onconnect(id) { - debug("socket connected with id %s", id); - this.id = id; - this.connected = true; - this.disconnected = false; - - _get(_getPrototypeOf(Socket.prototype), "emit", this).call( - this, - "connect" - ); - - this.emitBuffered(); - }, - /** - * Emit buffered events (received and emitted). - * - * @private - */ - }, - { - key: "emitBuffered", - value: function emitBuffered() { - var _this3 = this; - - this.receiveBuffer.forEach(function (args) { - return _this3.emitEvent(args); - }); - this.receiveBuffer = []; - this.sendBuffer.forEach(function (packet) { - return _this3.packet(packet); - }); - this.sendBuffer = []; - }, - /** - * Called upon server disconnect. - * - * @private - */ - }, - { - key: "ondisconnect", - value: function ondisconnect() { - debug("server disconnect (%s)", this.nsp); - this.destroy(); - this.onclose("io server disconnect"); - }, - /** - * Called upon forced client/server side disconnections, - * this method ensures the manager stops tracking us and - * that reconnections don't get triggered for this. - * - * @private - */ - }, - { - key: "destroy", - value: function destroy() { - if (this.subs) { - // clean subscriptions to avoid reconnections - this.subs.forEach(function (subDestroy) { - return subDestroy(); - }); - this.subs = undefined; - } - - this.io["_destroy"](this); - }, - /** - * Disconnects the socket manually. - * - * @return self - * @public - */ - }, - { - key: "disconnect", - value: function disconnect() { - if (this.connected) { - debug("performing disconnect (%s)", this.nsp); - this.packet({ - type: socket_io_parser_1.PacketType.DISCONNECT, - }); - } // remove socket from pool - - this.destroy(); - - if (this.connected) { - // fire events - this.onclose("io client disconnect"); - } - - return this; - }, - /** - * Alias for disconnect() - * - * @return self - * @public - */ - }, - { - key: "close", - value: function close() { - return this.disconnect(); - }, - /** - * Sets the compress flag. - * - * @param compress - if `true`, compresses the sending data - * @return self - * @public - */ - }, - { - key: "compress", - value: function compress(_compress) { - this.flags.compress = _compress; - return this; - }, - /** - * Sets a modifier for a subsequent event emission that the event message will be dropped when this socket is not - * ready to send messages. - * - * @returns self - * @public - */ - }, - { - key: "onAny", - - /** - * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the - * callback. - * - * @param listener - * @public - */ - value: function onAny(listener) { - this._anyListeners = this._anyListeners || []; - - this._anyListeners.push(listener); - - return this; - }, - /** - * Adds a listener that will be fired when any event is emitted. The event name is passed as the first argument to the - * callback. The listener is added to the beginning of the listeners array. - * - * @param listener - * @public - */ - }, - { - key: "prependAny", - value: function prependAny(listener) { - this._anyListeners = this._anyListeners || []; - - this._anyListeners.unshift(listener); - - return this; - }, - /** - * Removes the listener that will be fired when any event is emitted. - * - * @param listener - * @public - */ - }, - { - key: "offAny", - value: function offAny(listener) { - if (!this._anyListeners) { - return this; - } - - if (listener) { - var listeners = this._anyListeners; - - for (var i = 0; i < listeners.length; i++) { - if (listener === listeners[i]) { - listeners.splice(i, 1); - return this; - } - } - } else { - this._anyListeners = []; - } - - return this; - }, - /** - * Returns an array of listeners that are listening for any event that is specified. This array can be manipulated, - * e.g. to remove listeners. - * - * @public - */ - }, - { - key: "listenersAny", - value: function listenersAny() { - return this._anyListeners || []; - }, - }, - { - key: "active", - get: function get() { - return !!this.subs; - }, - }, - { - key: "volatile", - get: function get() { - this.flags["volatile"] = true; - return this; - }, - }, - ]); - - return Socket; - })(Emitter); - - exports.Socket = Socket; - - /***/ - }, - - /***/ "./build/url.js": - /*!**********************!*\ - !*** ./build/url.js ***! - \**********************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - "use strict"; - - Object.defineProperty(exports, "__esModule", { - value: true, - }); - exports.url = void 0; - - var parseuri = __webpack_require__( - /*! parseuri */ "./node_modules/parseuri/index.js" - ); - - var debug = __webpack_require__( - /*! debug */ "./node_modules/debug/src/browser.js" - )("socket.io-client:url"); - /** - * URL parser. - * - * @param uri - url - * @param path - the request path of the connection - * @param loc - An object meant to mimic window.location. - * Defaults to window.location. - * @public - */ - - function url(uri) { - var path = - arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : ""; - var loc = arguments.length > 2 ? arguments[2] : undefined; - var obj = uri; // default to window.location - - loc = loc || (typeof location !== "undefined" && location); - if (null == uri) uri = loc.protocol + "//" + loc.host; // relative path support - - if (typeof uri === "string") { - if ("/" === uri.charAt(0)) { - if ("/" === uri.charAt(1)) { - uri = loc.protocol + uri; - } else { - uri = loc.host + uri; - } - } - - if (!/^(https?|wss?):\/\//.test(uri)) { - debug("protocol-less url %s", uri); - - if ("undefined" !== typeof loc) { - uri = loc.protocol + "//" + uri; - } else { - uri = "https://" + uri; - } - } // parse - - debug("parse %s", uri); - obj = parseuri(uri); - } // make sure we treat `localhost:80` and `localhost` equally - - if (!obj.port) { - if (/^(http|ws)$/.test(obj.protocol)) { - obj.port = "80"; - } else if (/^(http|ws)s$/.test(obj.protocol)) { - obj.port = "443"; - } - } - - obj.path = obj.path || "/"; - var ipv6 = obj.host.indexOf(":") !== -1; - var host = ipv6 ? "[" + obj.host + "]" : obj.host; // define unique id - - obj.id = obj.protocol + "://" + host + ":" + obj.port + path; // define href - - obj.href = - obj.protocol + - "://" + - host + - (loc && loc.port === obj.port ? "" : ":" + obj.port); - return obj; - } - - exports.url = url; - - /***/ - }, - - /***/ "./node_modules/backo2/index.js": - /*!**************************************!*\ - !*** ./node_modules/backo2/index.js ***! - \**************************************/ - /*! no static exports found */ - /***/ function (module, exports) { - /** - * Expose `Backoff`. - */ - module.exports = Backoff; - /** - * Initialize backoff timer with `opts`. - * - * - `min` initial timeout in milliseconds [100] - * - `max` max timeout [10000] - * - `jitter` [0] - * - `factor` [2] - * - * @param {Object} opts - * @api public - */ - - function Backoff(opts) { - opts = opts || {}; - this.ms = opts.min || 100; - this.max = opts.max || 10000; - this.factor = opts.factor || 2; - this.jitter = opts.jitter > 0 && opts.jitter <= 1 ? opts.jitter : 0; - this.attempts = 0; - } - /** - * Return the backoff duration. - * - * @return {Number} - * @api public - */ - - Backoff.prototype.duration = function () { - var ms = this.ms * Math.pow(this.factor, this.attempts++); - - if (this.jitter) { - var rand = Math.random(); - var deviation = Math.floor(rand * this.jitter * ms); - ms = - (Math.floor(rand * 10) & 1) == 0 - ? ms - deviation - : ms + deviation; - } - - return Math.min(ms, this.max) | 0; - }; - /** - * Reset the number of attempts. - * - * @api public - */ - - Backoff.prototype.reset = function () { - this.attempts = 0; - }; - /** - * Set the minimum duration - * - * @api public - */ - - Backoff.prototype.setMin = function (min) { - this.ms = min; - }; - /** - * Set the maximum duration - * - * @api public - */ - - Backoff.prototype.setMax = function (max) { - this.max = max; - }; - /** - * Set the jitter - * - * @api public - */ - - Backoff.prototype.setJitter = function (jitter) { - this.jitter = jitter; - }; - - /***/ - }, - - /***/ "./node_modules/component-emitter/index.js": - /*!*************************************************!*\ - !*** ./node_modules/component-emitter/index.js ***! - \*************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - /** - * Expose `Emitter`. - */ - if (true) { - module.exports = Emitter; - } - /** - * Initialize a new `Emitter`. - * - * @api public - */ - - function Emitter(obj) { - if (obj) return mixin(obj); - } - - /** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - - function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - - return obj; - } - /** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.on = Emitter.prototype.addEventListener = function ( - event, - fn - ) { - this._callbacks = this._callbacks || {}; - (this._callbacks["$" + event] = - this._callbacks["$" + event] || []).push(fn); - return this; - }; - /** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.once = function (event, fn) { - function on() { - this.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; - }; - /** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - - Emitter.prototype.off = - Emitter.prototype.removeListener = - Emitter.prototype.removeAllListeners = - Emitter.prototype.removeEventListener = - function (event, fn) { - this._callbacks = this._callbacks || {}; // all - - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } // specific event - - var callbacks = this._callbacks["$" + event]; - if (!callbacks) return this; // remove all handlers - - if (1 == arguments.length) { - delete this._callbacks["$" + event]; - return this; - } // remove specific handler - - var cb; - - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } // Remove event specific arrays for event types that no - // one is subscribed for to avoid memory leak. - - if (callbacks.length === 0) { - delete this._callbacks["$" + event]; - } - - return this; - }; - /** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - - Emitter.prototype.emit = function (event) { - this._callbacks = this._callbacks || {}; - var args = new Array(arguments.length - 1), - callbacks = this._callbacks["$" + event]; - - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - - if (callbacks) { - callbacks = callbacks.slice(0); - - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; - }; - /** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - - Emitter.prototype.listeners = function (event) { - this._callbacks = this._callbacks || {}; - return this._callbacks["$" + event] || []; - }; - /** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - - Emitter.prototype.hasListeners = function (event) { - return !!this.listeners(event).length; - }; - - /***/ - }, - - /***/ "./node_modules/debug/src/browser.js": - /*!*******************************************!*\ - !*** ./node_modules/debug/src/browser.js ***! - \*******************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - /* eslint-env browser */ - - /** - * This is the web browser implementation of `debug()`. - */ - exports.formatArgs = formatArgs; - exports.save = save; - exports.load = load; - exports.useColors = useColors; - exports.storage = localstorage(); - - exports.destroy = (function () { - var warned = false; - return function () { - if (!warned) { - warned = true; - console.warn( - "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`." - ); - } - }; - })(); - /** - * Colors. - */ - - exports.colors = [ - "#0000CC", - "#0000FF", - "#0033CC", - "#0033FF", - "#0066CC", - "#0066FF", - "#0099CC", - "#0099FF", - "#00CC00", - "#00CC33", - "#00CC66", - "#00CC99", - "#00CCCC", - "#00CCFF", - "#3300CC", - "#3300FF", - "#3333CC", - "#3333FF", - "#3366CC", - "#3366FF", - "#3399CC", - "#3399FF", - "#33CC00", - "#33CC33", - "#33CC66", - "#33CC99", - "#33CCCC", - "#33CCFF", - "#6600CC", - "#6600FF", - "#6633CC", - "#6633FF", - "#66CC00", - "#66CC33", - "#9900CC", - "#9900FF", - "#9933CC", - "#9933FF", - "#99CC00", - "#99CC33", - "#CC0000", - "#CC0033", - "#CC0066", - "#CC0099", - "#CC00CC", - "#CC00FF", - "#CC3300", - "#CC3333", - "#CC3366", - "#CC3399", - "#CC33CC", - "#CC33FF", - "#CC6600", - "#CC6633", - "#CC9900", - "#CC9933", - "#CCCC00", - "#CCCC33", - "#FF0000", - "#FF0033", - "#FF0066", - "#FF0099", - "#FF00CC", - "#FF00FF", - "#FF3300", - "#FF3333", - "#FF3366", - "#FF3399", - "#FF33CC", - "#FF33FF", - "#FF6600", - "#FF6633", - "#FF9900", - "#FF9933", - "#FFCC00", - "#FFCC33", - ]; - /** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - // eslint-disable-next-line complexity - - function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if ( - typeof window !== "undefined" && - window.process && - (window.process.type === "renderer" || window.process.__nwjs) - ) { - return true; - } // Internet Explorer and Edge do not support colors. - - if ( - typeof navigator !== "undefined" && - navigator.userAgent && - navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/) - ) { - return false; - } // Is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - - return ( - (typeof document !== "undefined" && - document.documentElement && - document.documentElement.style && - document.documentElement.style.WebkitAppearance) || // Is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== "undefined" && - window.console && - (window.console.firebug || - (window.console.exception && window.console.table))) || // Is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== "undefined" && - navigator.userAgent && - navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && - parseInt(RegExp.$1, 10) >= 31) || // Double check webkit in userAgent just in case we are in a worker - (typeof navigator !== "undefined" && - navigator.userAgent && - navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)) - ); - } - /** - * Colorize log arguments if enabled. - * - * @api public - */ - - function formatArgs(args) { - args[0] = - (this.useColors ? "%c" : "") + - this.namespace + - (this.useColors ? " %c" : " ") + - args[0] + - (this.useColors ? "%c " : " ") + - "+" + - module.exports.humanize(this.diff); - - if (!this.useColors) { - return; - } - - var c = "color: " + this.color; - args.splice(1, 0, c, "color: inherit"); // The final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function (match) { - if (match === "%%") { - return; - } - - index++; - - if (match === "%c") { - // We only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - args.splice(lastC, 0, c); - } - /** - * Invokes `console.debug()` when available. - * No-op when `console.debug` is not a "function". - * If `console.debug` is not available, falls back - * to `console.log`. - * - * @api public - */ - - exports.log = console.debug || console.log || function () {}; - /** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - - function save(namespaces) { - try { - if (namespaces) { - exports.storage.setItem("debug", namespaces); - } else { - exports.storage.removeItem("debug"); - } - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - } - /** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - - function load() { - var r; - - try { - r = exports.storage.getItem("debug"); - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - - if (!r && typeof process !== "undefined" && "env" in process) { - r = process.env.DEBUG; - } - - return r; - } - /** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - - function localstorage() { - try { - // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context - // The Browser also has localStorage in the global context. - return localStorage; - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - } - - module.exports = __webpack_require__( - /*! ./common */ "./node_modules/debug/src/common.js" - )(exports); - var formatters = module.exports.formatters; - /** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - - formatters.j = function (v) { - try { - return JSON.stringify(v); - } catch (error) { - return "[UnexpectedJSONParseError]: " + error.message; - } - }; - - /***/ - }, - - /***/ "./node_modules/debug/src/common.js": - /*!******************************************!*\ - !*** ./node_modules/debug/src/common.js ***! - \******************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - function _toConsumableArray(arr) { - return ( - _arrayWithoutHoles(arr) || - _iterableToArray(arr) || - _unsupportedIterableToArray(arr) || - _nonIterableSpread() - ); - } - - function _nonIterableSpread() { - throw new TypeError( - "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method." - ); - } - - function _unsupportedIterableToArray(o, minLen) { - if (!o) return; - if (typeof o === "string") return _arrayLikeToArray(o, minLen); - var n = Object.prototype.toString.call(o).slice(8, -1); - if (n === "Object" && o.constructor) n = o.constructor.name; - if (n === "Map" || n === "Set") return Array.from(o); - if ( - n === "Arguments" || - /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n) - ) - return _arrayLikeToArray(o, minLen); - } - - function _iterableToArray(iter) { - if ( - typeof Symbol !== "undefined" && - Symbol.iterator in Object(iter) - ) - return Array.from(iter); - } - - function _arrayWithoutHoles(arr) { - if (Array.isArray(arr)) return _arrayLikeToArray(arr); - } - - function _arrayLikeToArray(arr, len) { - if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) { - arr2[i] = arr[i]; - } - return arr2; - } - - /** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - */ - function setup(env) { - createDebug.debug = createDebug; - createDebug["default"] = createDebug; - createDebug.coerce = coerce; - createDebug.disable = disable; - createDebug.enable = enable; - createDebug.enabled = enabled; - createDebug.humanize = __webpack_require__( - /*! ms */ "./node_modules/ms/index.js" - ); - createDebug.destroy = destroy; - Object.keys(env).forEach(function (key) { - createDebug[key] = env[key]; - }); - /** - * The currently active debug mode names, and names to skip. - */ - - createDebug.names = []; - createDebug.skips = []; - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - - createDebug.formatters = {}; - /** - * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored - * @return {Number|String} An ANSI color code for the given namespace - * @api private - */ - - function selectColor(namespace) { - var hash = 0; - - for (var i = 0; i < namespace.length; i++) { - hash = (hash << 5) - hash + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return createDebug.colors[ - Math.abs(hash) % createDebug.colors.length - ]; - } - - createDebug.selectColor = selectColor; - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - - function createDebug(namespace) { - var prevTime; - var enableOverride = null; - - function debug() { - for ( - var _len = arguments.length, args = new Array(_len), _key = 0; - _key < _len; - _key++ - ) { - args[_key] = arguments[_key]; - } - - // Disabled? - if (!debug.enabled) { - return; - } - - var self = debug; // Set `diff` timestamp - - var curr = Number(new Date()); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - args[0] = createDebug.coerce(args[0]); - - if (typeof args[0] !== "string") { - // Anything else let's inspect with %O - args.unshift("%O"); - } // Apply any `formatters` transformations - - var index = 0; - args[0] = args[0].replace( - /%([a-zA-Z%])/g, - function (match, format) { - // If we encounter an escaped % then don't increase the array index - if (match === "%%") { - return "%"; - } - - index++; - var formatter = createDebug.formatters[format]; - - if (typeof formatter === "function") { - var val = args[index]; - match = formatter.call(self, val); // Now we need to remove `args[index]` since it's inlined in the `format` - - args.splice(index, 1); - index--; - } - - return match; - } - ); // Apply env-specific formatting (colors, etc.) - - createDebug.formatArgs.call(self, args); - var logFn = self.log || createDebug.log; - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.useColors = createDebug.useColors(); - debug.color = createDebug.selectColor(namespace); - debug.extend = extend; - debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. - - Object.defineProperty(debug, "enabled", { - enumerable: true, - configurable: false, - get: function get() { - return enableOverride === null - ? createDebug.enabled(namespace) - : enableOverride; - }, - set: function set(v) { - enableOverride = v; - }, - }); // Env-specific initialization logic for debug instances - - if (typeof createDebug.init === "function") { - createDebug.init(debug); - } - - return debug; - } - - function extend(namespace, delimiter) { - var newDebug = createDebug( - this.namespace + - (typeof delimiter === "undefined" ? ":" : delimiter) + - namespace - ); - newDebug.log = this.log; - return newDebug; - } - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - - function enable(namespaces) { - createDebug.save(namespaces); - createDebug.names = []; - createDebug.skips = []; - var i; - var split = ( - typeof namespaces === "string" ? namespaces : "" - ).split(/[\s,]+/); - var len = split.length; - - for (i = 0; i < len; i++) { - if (!split[i]) { - // ignore empty strings - continue; - } - - namespaces = split[i].replace(/\*/g, ".*?"); - - if (namespaces[0] === "-") { - createDebug.skips.push( - new RegExp("^" + namespaces.substr(1) + "$") - ); - } else { - createDebug.names.push(new RegExp("^" + namespaces + "$")); - } - } - } - /** - * Disable debug output. - * - * @return {String} namespaces - * @api public - */ - - function disable() { - var namespaces = [] - .concat( - _toConsumableArray(createDebug.names.map(toNamespace)), - _toConsumableArray( - createDebug.skips - .map(toNamespace) - .map(function (namespace) { - return "-" + namespace; - }) - ) - ) - .join(","); - createDebug.enable(""); - return namespaces; - } - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - - function enabled(name) { - if (name[name.length - 1] === "*") { - return true; - } - - var i; - var len; - - for (i = 0, len = createDebug.skips.length; i < len; i++) { - if (createDebug.skips[i].test(name)) { - return false; - } - } - - for (i = 0, len = createDebug.names.length; i < len; i++) { - if (createDebug.names[i].test(name)) { - return true; - } - } - - return false; - } - /** - * Convert regexp to namespace - * - * @param {RegExp} regxep - * @return {String} namespace - * @api private - */ - - function toNamespace(regexp) { - return regexp - .toString() - .substring(2, regexp.toString().length - 2) - .replace(/\.\*\?$/, "*"); - } - /** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - - function coerce(val) { - if (val instanceof Error) { - return val.stack || val.message; - } - - return val; - } - /** - * XXX DO NOT USE. This is a temporary stub function. - * XXX It WILL be removed in the next major release. - */ - - function destroy() { - console.warn( - "Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`." - ); - } - - createDebug.enable(createDebug.load()); - return createDebug; - } - - module.exports = setup; - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/globalThis.browser.js": - /*!*****************************************************************!*\ - !*** ./node_modules/engine.io-client/lib/globalThis.browser.js ***! - \*****************************************************************/ - /*! no static exports found */ - /***/ function (module, exports) { - module.exports = (function () { - if (typeof self !== "undefined") { - return self; - } else if (typeof window !== "undefined") { - return window; - } else { - return Function("return this")(); - } - })(); - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/index.js": - /*!****************************************************!*\ - !*** ./node_modules/engine.io-client/lib/index.js ***! - \****************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - var Socket = __webpack_require__( - /*! ./socket */ "./node_modules/engine.io-client/lib/socket.js" - ); - - module.exports = function (uri, opts) { - return new Socket(uri, opts); - }; - /** - * Expose deps for legacy compatibility - * and standalone browser access. - */ - - module.exports.Socket = Socket; - module.exports.protocol = Socket.protocol; // this is an int - - module.exports.Transport = __webpack_require__( - /*! ./transport */ "./node_modules/engine.io-client/lib/transport.js" - ); - module.exports.transports = __webpack_require__( - /*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js" - ); - module.exports.parser = __webpack_require__( - /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" - ); - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/socket.js": - /*!*****************************************************!*\ - !*** ./node_modules/engine.io-client/lib/socket.js ***! - \*****************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - function _extends() { - _extends = - Object.assign || - function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - return target; - }; - return _extends.apply(this, arguments); - } - - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) - _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError( - "Super expression must either be null or a function" - ); - } - subClass.prototype = Object.create( - superClass && superClass.prototype, - { - constructor: { - value: subClass, - writable: true, - configurable: true, - }, - } - ); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _possibleConstructorReturn(self, call) { - if ( - call && - (_typeof(call) === "object" || typeof call === "function") - ) { - return call; - } - return _assertThisInitialized(self); - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - } - return self; - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) - return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Date.prototype.toString.call( - Reflect.construct(Date, [], function () {}) - ); - return true; - } catch (e) { - return false; - } - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - var transports = __webpack_require__( - /*! ./transports/index */ "./node_modules/engine.io-client/lib/transports/index.js" - ); - - var Emitter = __webpack_require__( - /*! component-emitter */ "./node_modules/component-emitter/index.js" - ); - - var debug = __webpack_require__( - /*! debug */ "./node_modules/debug/src/browser.js" - )("engine.io-client:socket"); - - var parser = __webpack_require__( - /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" - ); - - var parseuri = __webpack_require__( - /*! parseuri */ "./node_modules/parseuri/index.js" - ); - - var parseqs = __webpack_require__( - /*! parseqs */ "./node_modules/parseqs/index.js" - ); - - var Socket = /*#__PURE__*/ (function (_Emitter) { - _inherits(Socket, _Emitter); - - var _super = _createSuper(Socket); - - /** - * Socket constructor. - * - * @param {String|Object} uri or options - * @param {Object} options - * @api public - */ - function Socket(uri) { - var _this; - - var opts = - arguments.length > 1 && arguments[1] !== undefined - ? arguments[1] - : {}; - - _classCallCheck(this, Socket); - - _this = _super.call(this); - - if (uri && "object" === _typeof(uri)) { - opts = uri; - uri = null; - } - - if (uri) { - uri = parseuri(uri); - opts.hostname = uri.host; - opts.secure = - uri.protocol === "https" || uri.protocol === "wss"; - opts.port = uri.port; - if (uri.query) opts.query = uri.query; - } else if (opts.host) { - opts.hostname = parseuri(opts.host).host; - } - - _this.secure = - null != opts.secure - ? opts.secure - : typeof location !== "undefined" && - "https:" === location.protocol; - - if (opts.hostname && !opts.port) { - // if no port is specified manually, use the protocol default - opts.port = _this.secure ? "443" : "80"; - } - - _this.hostname = - opts.hostname || - (typeof location !== "undefined" - ? location.hostname - : "localhost"); - _this.port = - opts.port || - (typeof location !== "undefined" && location.port - ? location.port - : _this.secure - ? 443 - : 80); - _this.transports = opts.transports || ["polling", "websocket"]; - _this.readyState = ""; - _this.writeBuffer = []; - _this.prevBufferLen = 0; - _this.opts = _extends( - { - path: "/engine.io", - agent: false, - withCredentials: false, - upgrade: true, - jsonp: true, - timestampParam: "t", - rememberUpgrade: false, - rejectUnauthorized: true, - perMessageDeflate: { - threshold: 1024, - }, - transportOptions: {}, - }, - opts - ); - _this.opts.path = _this.opts.path.replace(/\/$/, "") + "/"; - - if (typeof _this.opts.query === "string") { - _this.opts.query = parseqs.decode(_this.opts.query); - } // set on handshake - - _this.id = null; - _this.upgrades = null; - _this.pingInterval = null; - _this.pingTimeout = null; // set on heartbeat - - _this.pingTimeoutTimer = null; - - if (typeof addEventListener === "function") { - addEventListener( - "beforeunload", - function () { - if (_this.transport) { - // silently close the transport - _this.transport.removeAllListeners(); - - _this.transport.close(); - } - }, - false - ); - } - - _this.open(); - - return _this; - } - /** - * Creates transport of the given type. - * - * @param {String} transport name - * @return {Transport} - * @api private - */ - - _createClass(Socket, [ - { - key: "createTransport", - value: function createTransport(name) { - debug('creating transport "%s"', name); - var query = clone(this.opts.query); // append engine.io protocol identifier - - query.EIO = parser.protocol; // transport name - - query.transport = name; // session id if we already have one - - if (this.id) query.sid = this.id; - - var opts = _extends( - {}, - this.opts.transportOptions[name], - this.opts, - { - query: query, - socket: this, - hostname: this.hostname, - secure: this.secure, - port: this.port, - } - ); - - debug("options: %j", opts); - return new transports[name](opts); - }, - /** - * Initializes transport to use and starts probe. - * - * @api private - */ - }, - { - key: "open", - value: function open() { - var transport; - - if ( - this.opts.rememberUpgrade && - Socket.priorWebsocketSuccess && - this.transports.indexOf("websocket") !== -1 - ) { - transport = "websocket"; - } else if (0 === this.transports.length) { - // Emit error on next tick so it can be listened to - var self = this; - setTimeout(function () { - self.emit("error", "No transports available"); - }, 0); - return; - } else { - transport = this.transports[0]; - } - - this.readyState = "opening"; // Retry with the next transport if the transport is disabled (jsonp: false) - - try { - transport = this.createTransport(transport); - } catch (e) { - debug("error while creating transport: %s", e); - this.transports.shift(); - this.open(); - return; - } - - transport.open(); - this.setTransport(transport); - }, - /** - * Sets the current transport. Disables the existing one (if any). - * - * @api private - */ - }, - { - key: "setTransport", - value: function setTransport(transport) { - debug("setting transport %s", transport.name); - var self = this; - - if (this.transport) { - debug( - "clearing existing transport %s", - this.transport.name - ); - this.transport.removeAllListeners(); - } // set up transport - - this.transport = transport; // set up transport listeners - - transport - .on("drain", function () { - self.onDrain(); - }) - .on("packet", function (packet) { - self.onPacket(packet); - }) - .on("error", function (e) { - self.onError(e); - }) - .on("close", function () { - self.onClose("transport close"); - }); - }, - /** - * Probes a transport. - * - * @param {String} transport name - * @api private - */ - }, - { - key: "probe", - value: function probe(name) { - debug('probing transport "%s"', name); - var transport = this.createTransport(name, { - probe: 1, - }); - var failed = false; - var self = this; - Socket.priorWebsocketSuccess = false; - - function onTransportOpen() { - if (self.onlyBinaryUpgrades) { - var upgradeLosesBinary = - !this.supportsBinary && self.transport.supportsBinary; - failed = failed || upgradeLosesBinary; - } - - if (failed) return; - debug('probe transport "%s" opened', name); - transport.send([ - { - type: "ping", - data: "probe", - }, - ]); - transport.once("packet", function (msg) { - if (failed) return; - - if ("pong" === msg.type && "probe" === msg.data) { - debug('probe transport "%s" pong', name); - self.upgrading = true; - self.emit("upgrading", transport); - if (!transport) return; - Socket.priorWebsocketSuccess = - "websocket" === transport.name; - debug( - 'pausing current transport "%s"', - self.transport.name - ); - self.transport.pause(function () { - if (failed) return; - if ("closed" === self.readyState) return; - debug( - "changing transport and sending upgrade packet" - ); - cleanup(); - self.setTransport(transport); - transport.send([ - { - type: "upgrade", - }, - ]); - self.emit("upgrade", transport); - transport = null; - self.upgrading = false; - self.flush(); - }); - } else { - debug('probe transport "%s" failed', name); - var err = new Error("probe error"); - err.transport = transport.name; - self.emit("upgradeError", err); - } - }); - } - - function freezeTransport() { - if (failed) return; // Any callback called by transport should be ignored since now - - failed = true; - cleanup(); - transport.close(); - transport = null; - } // Handle any error that happens while probing - - function onerror(err) { - var error = new Error("probe error: " + err); - error.transport = transport.name; - freezeTransport(); - debug( - 'probe transport "%s" failed because of error: %s', - name, - err - ); - self.emit("upgradeError", error); - } - - function onTransportClose() { - onerror("transport closed"); - } // When the socket is closed while we're probing - - function onclose() { - onerror("socket closed"); - } // When the socket is upgraded while we're probing - - function onupgrade(to) { - if (transport && to.name !== transport.name) { - debug( - '"%s" works - aborting "%s"', - to.name, - transport.name - ); - freezeTransport(); - } - } // Remove all listeners on the transport and on self - - function cleanup() { - transport.removeListener("open", onTransportOpen); - transport.removeListener("error", onerror); - transport.removeListener("close", onTransportClose); - self.removeListener("close", onclose); - self.removeListener("upgrading", onupgrade); - } - - transport.once("open", onTransportOpen); - transport.once("error", onerror); - transport.once("close", onTransportClose); - this.once("close", onclose); - this.once("upgrading", onupgrade); - transport.open(); - }, - /** - * Called when connection is deemed open. - * - * @api public - */ - }, - { - key: "onOpen", - value: function onOpen() { - debug("socket open"); - this.readyState = "open"; - Socket.priorWebsocketSuccess = - "websocket" === this.transport.name; - this.emit("open"); - this.flush(); // we check for `readyState` in case an `open` - // listener already closed the socket - - if ( - "open" === this.readyState && - this.opts.upgrade && - this.transport.pause - ) { - debug("starting upgrade probes"); - var i = 0; - var l = this.upgrades.length; - - for (; i < l; i++) { - this.probe(this.upgrades[i]); - } - } - }, - /** - * Handles a packet. - * - * @api private - */ - }, - { - key: "onPacket", - value: function onPacket(packet) { - if ( - "opening" === this.readyState || - "open" === this.readyState || - "closing" === this.readyState - ) { - debug( - 'socket receive: type "%s", data "%s"', - packet.type, - packet.data - ); - this.emit("packet", packet); // Socket is live - any packet counts - - this.emit("heartbeat"); - - switch (packet.type) { - case "open": - this.onHandshake(JSON.parse(packet.data)); - break; - - case "ping": - this.resetPingTimeout(); - this.sendPacket("pong"); - this.emit("pong"); - break; - - case "error": - var err = new Error("server error"); - err.code = packet.data; - this.onError(err); - break; - - case "message": - this.emit("data", packet.data); - this.emit("message", packet.data); - break; - } - } else { - debug( - 'packet received with socket readyState "%s"', - this.readyState - ); - } - }, - /** - * Called upon handshake completion. - * - * @param {Object} handshake obj - * @api private - */ - }, - { - key: "onHandshake", - value: function onHandshake(data) { - this.emit("handshake", data); - this.id = data.sid; - this.transport.query.sid = data.sid; - this.upgrades = this.filterUpgrades(data.upgrades); - this.pingInterval = data.pingInterval; - this.pingTimeout = data.pingTimeout; - this.onOpen(); // In case open handler closes socket - - if ("closed" === this.readyState) return; - this.resetPingTimeout(); - }, - /** - * Sets and resets ping timeout timer based on server pings. - * - * @api private - */ - }, - { - key: "resetPingTimeout", - value: function resetPingTimeout() { - var _this2 = this; - - clearTimeout(this.pingTimeoutTimer); - this.pingTimeoutTimer = setTimeout(function () { - _this2.onClose("ping timeout"); - }, this.pingInterval + this.pingTimeout); - }, - /** - * Called on `drain` event - * - * @api private - */ - }, - { - key: "onDrain", - value: function onDrain() { - this.writeBuffer.splice(0, this.prevBufferLen); // setting prevBufferLen = 0 is very important - // for example, when upgrading, upgrade packet is sent over, - // and a nonzero prevBufferLen could cause problems on `drain` - - this.prevBufferLen = 0; - - if (0 === this.writeBuffer.length) { - this.emit("drain"); - } else { - this.flush(); - } - }, - /** - * Flush write buffers. - * - * @api private - */ - }, - { - key: "flush", - value: function flush() { - if ( - "closed" !== this.readyState && - this.transport.writable && - !this.upgrading && - this.writeBuffer.length - ) { - debug( - "flushing %d packets in socket", - this.writeBuffer.length - ); - this.transport.send(this.writeBuffer); // keep track of current length of writeBuffer - // splice writeBuffer and callbackBuffer on `drain` - - this.prevBufferLen = this.writeBuffer.length; - this.emit("flush"); - } - }, - /** - * Sends a message. - * - * @param {String} message. - * @param {Function} callback function. - * @param {Object} options. - * @return {Socket} for chaining. - * @api public - */ - }, - { - key: "write", - value: function write(msg, options, fn) { - this.sendPacket("message", msg, options, fn); - return this; - }, - }, - { - key: "send", - value: function send(msg, options, fn) { - this.sendPacket("message", msg, options, fn); - return this; - }, - /** - * Sends a packet. - * - * @param {String} packet type. - * @param {String} data. - * @param {Object} options. - * @param {Function} callback function. - * @api private - */ - }, - { - key: "sendPacket", - value: function sendPacket(type, data, options, fn) { - if ("function" === typeof data) { - fn = data; - data = undefined; - } - - if ("function" === typeof options) { - fn = options; - options = null; - } - - if ( - "closing" === this.readyState || - "closed" === this.readyState - ) { - return; - } - - options = options || {}; - options.compress = false !== options.compress; - var packet = { - type: type, - data: data, - options: options, - }; - this.emit("packetCreate", packet); - this.writeBuffer.push(packet); - if (fn) this.once("flush", fn); - this.flush(); - }, - /** - * Closes the connection. - * - * @api private - */ - }, - { - key: "close", - value: function close() { - var self = this; - - if ( - "opening" === this.readyState || - "open" === this.readyState - ) { - this.readyState = "closing"; - - if (this.writeBuffer.length) { - this.once("drain", function () { - if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - }); - } else if (this.upgrading) { - waitForUpgrade(); - } else { - close(); - } - } - - function close() { - self.onClose("forced close"); - debug("socket closing - telling transport to close"); - self.transport.close(); - } - - function cleanupAndClose() { - self.removeListener("upgrade", cleanupAndClose); - self.removeListener("upgradeError", cleanupAndClose); - close(); - } - - function waitForUpgrade() { - // wait for upgrade to finish since we can't send packets while pausing a transport - self.once("upgrade", cleanupAndClose); - self.once("upgradeError", cleanupAndClose); - } - - return this; - }, - /** - * Called upon transport error - * - * @api private - */ - }, - { - key: "onError", - value: function onError(err) { - debug("socket error %j", err); - Socket.priorWebsocketSuccess = false; - this.emit("error", err); - this.onClose("transport error", err); - }, - /** - * Called upon transport close. - * - * @api private - */ - }, - { - key: "onClose", - value: function onClose(reason, desc) { - if ( - "opening" === this.readyState || - "open" === this.readyState || - "closing" === this.readyState - ) { - debug('socket close with reason: "%s"', reason); - var self = this; // clear timers - - clearTimeout(this.pingIntervalTimer); - clearTimeout(this.pingTimeoutTimer); // stop event from firing again for transport - - this.transport.removeAllListeners("close"); // ensure transport won't stay open - - this.transport.close(); // ignore further transport communication - - this.transport.removeAllListeners(); // set ready state - - this.readyState = "closed"; // clear session id - - this.id = null; // emit close event - - this.emit("close", reason, desc); // clean buffers after, so users can still - // grab the buffers on `close` event - - self.writeBuffer = []; - self.prevBufferLen = 0; - } - }, - /** - * Filters upgrades, returning only those matching client transports. - * - * @param {Array} server upgrades - * @api private - * - */ - }, - { - key: "filterUpgrades", - value: function filterUpgrades(upgrades) { - var filteredUpgrades = []; - var i = 0; - var j = upgrades.length; - - for (; i < j; i++) { - if (~this.transports.indexOf(upgrades[i])) - filteredUpgrades.push(upgrades[i]); - } - - return filteredUpgrades; - }, - }, - ]); - - return Socket; - })(Emitter); - - Socket.priorWebsocketSuccess = false; - /** - * Protocol version. - * - * @api public - */ - - Socket.protocol = parser.protocol; // this is an int - - function clone(obj) { - var o = {}; - - for (var i in obj) { - if (obj.hasOwnProperty(i)) { - o[i] = obj[i]; - } - } - - return o; - } - - module.exports = Socket; - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/transport.js": - /*!********************************************************!*\ - !*** ./node_modules/engine.io-client/lib/transport.js ***! - \********************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) - _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError( - "Super expression must either be null or a function" - ); - } - subClass.prototype = Object.create( - superClass && superClass.prototype, - { - constructor: { - value: subClass, - writable: true, - configurable: true, - }, - } - ); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _possibleConstructorReturn(self, call) { - if ( - call && - (_typeof(call) === "object" || typeof call === "function") - ) { - return call; - } - return _assertThisInitialized(self); - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - } - return self; - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) - return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Date.prototype.toString.call( - Reflect.construct(Date, [], function () {}) - ); - return true; - } catch (e) { - return false; - } - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - var parser = __webpack_require__( - /*! engine.io-parser */ "./node_modules/engine.io-parser/lib/index.js" - ); - - var Emitter = __webpack_require__( - /*! component-emitter */ "./node_modules/component-emitter/index.js" - ); - - var Transport = /*#__PURE__*/ (function (_Emitter) { - _inherits(Transport, _Emitter); - - var _super = _createSuper(Transport); - - /** - * Transport abstract constructor. - * - * @param {Object} options. - * @api private - */ - function Transport(opts) { - var _this; - - _classCallCheck(this, Transport); - - _this = _super.call(this); - _this.opts = opts; - _this.query = opts.query; - _this.readyState = ""; - _this.socket = opts.socket; - return _this; - } - /** - * Emits an error. - * - * @param {String} str - * @return {Transport} for chaining - * @api public - */ - - _createClass(Transport, [ - { - key: "onError", - value: function onError(msg, desc) { - var err = new Error(msg); - err.type = "TransportError"; - err.description = desc; - this.emit("error", err); - return this; - }, - /** - * Opens the transport. - * - * @api public - */ - }, - { - key: "open", - value: function open() { - if ("closed" === this.readyState || "" === this.readyState) { - this.readyState = "opening"; - this.doOpen(); - } - - return this; - }, - /** - * Closes the transport. - * - * @api private - */ - }, - { - key: "close", - value: function close() { - if ( - "opening" === this.readyState || - "open" === this.readyState - ) { - this.doClose(); - this.onClose(); - } - - return this; - }, - /** - * Sends multiple packets. - * - * @param {Array} packets - * @api private - */ - }, - { - key: "send", - value: function send(packets) { - if ("open" === this.readyState) { - this.write(packets); - } else { - throw new Error("Transport not open"); - } - }, - /** - * Called upon open - * - * @api private - */ - }, - { - key: "onOpen", - value: function onOpen() { - this.readyState = "open"; - this.writable = true; - this.emit("open"); - }, - /** - * Called with data. - * - * @param {String} data - * @api private - */ - }, - { - key: "onData", - value: function onData(data) { - var packet = parser.decodePacket( - data, - this.socket.binaryType - ); - this.onPacket(packet); - }, - /** - * Called with a decoded packet. - */ - }, - { - key: "onPacket", - value: function onPacket(packet) { - this.emit("packet", packet); - }, - /** - * Called upon close. - * - * @api private - */ - }, - { - key: "onClose", - value: function onClose() { - this.readyState = "closed"; - this.emit("close"); - }, - }, - ]); - - return Transport; - })(Emitter); - - module.exports = Transport; - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/transports/index.js": - /*!***************************************************************!*\ - !*** ./node_modules/engine.io-client/lib/transports/index.js ***! - \***************************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - var XMLHttpRequest = __webpack_require__( - /*! xmlhttprequest-ssl */ "./node_modules/engine.io-client/lib/xmlhttprequest.js" - ); - - var XHR = __webpack_require__( - /*! ./polling-xhr */ "./node_modules/engine.io-client/lib/transports/polling-xhr.js" - ); - - var JSONP = __webpack_require__( - /*! ./polling-jsonp */ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js" - ); - - var websocket = __webpack_require__( - /*! ./websocket */ "./node_modules/engine.io-client/lib/transports/websocket.js" - ); - - exports.polling = polling; - exports.websocket = websocket; - /** - * Polling transport polymorphic constructor. - * Decides on xhr vs jsonp based on feature detection. - * - * @api private - */ - - function polling(opts) { - var xhr; - var xd = false; - var xs = false; - var jsonp = false !== opts.jsonp; - - if (typeof location !== "undefined") { - var isSSL = "https:" === location.protocol; - var port = location.port; // some user agents have empty `location.port` - - if (!port) { - port = isSSL ? 443 : 80; - } - - xd = opts.hostname !== location.hostname || port !== opts.port; - xs = opts.secure !== isSSL; - } - - opts.xdomain = xd; - opts.xscheme = xs; - xhr = new XMLHttpRequest(opts); - - if ("open" in xhr && !opts.forceJSONP) { - return new XHR(opts); - } else { - if (!jsonp) throw new Error("JSONP disabled"); - return new JSONP(opts); - } - } - - /***/ - }, - - /***/ "./node_modules/engine.io-client/lib/transports/polling-jsonp.js": - /*!***********************************************************************!*\ - !*** ./node_modules/engine.io-client/lib/transports/polling-jsonp.js ***! - \***********************************************************************/ - /*! no static exports found */ - /***/ function (module, exports, __webpack_require__) { - function _typeof(obj) { - "@babel/helpers - typeof"; - if ( - typeof Symbol === "function" && - typeof Symbol.iterator === "symbol" - ) { - _typeof = function _typeof(obj) { - return typeof obj; - }; - } else { - _typeof = function _typeof(obj) { - return obj && - typeof Symbol === "function" && - obj.constructor === Symbol && - obj !== Symbol.prototype - ? "symbol" - : typeof obj; - }; - } - return _typeof(obj); - } - - function _classCallCheck(instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } - } - - function _defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - function _createClass(Constructor, protoProps, staticProps) { - if (protoProps) - _defineProperties(Constructor.prototype, protoProps); - if (staticProps) _defineProperties(Constructor, staticProps); - return Constructor; - } - - function _get(target, property, receiver) { - if (typeof Reflect !== "undefined" && Reflect.get) { - _get = Reflect.get; - } else { - _get = function _get(target, property, receiver) { - var base = _superPropBase(target, property); - if (!base) return; - var desc = Object.getOwnPropertyDescriptor(base, property); - if (desc.get) { - return desc.get.call(receiver); - } - return desc.value; - }; - } - return _get(target, property, receiver || target); - } - - function _superPropBase(object, property) { - while (!Object.prototype.hasOwnProperty.call(object, property)) { - object = _getPrototypeOf(object); - if (object === null) break; - } - return object; - } - - function _inherits(subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError( - "Super expression must either be null or a function" - ); - } - subClass.prototype = Object.create( - superClass && superClass.prototype, - { - constructor: { - value: subClass, - writable: true, - configurable: true, - }, - } - ); - if (superClass) _setPrototypeOf(subClass, superClass); - } - - function _setPrototypeOf(o, p) { - _setPrototypeOf = - Object.setPrototypeOf || - function _setPrototypeOf(o, p) { - o.__proto__ = p; - return o; - }; - return _setPrototypeOf(o, p); - } - - function _createSuper(Derived) { - var hasNativeReflectConstruct = _isNativeReflectConstruct(); - return function _createSuperInternal() { - var Super = _getPrototypeOf(Derived), - result; - if (hasNativeReflectConstruct) { - var NewTarget = _getPrototypeOf(this).constructor; - result = Reflect.construct(Super, arguments, NewTarget); - } else { - result = Super.apply(this, arguments); - } - return _possibleConstructorReturn(this, result); - }; - } - - function _possibleConstructorReturn(self, call) { - if ( - call && - (_typeof(call) === "object" || typeof call === "function") - ) { - return call; - } - return _assertThisInitialized(self); - } - - function _assertThisInitialized(self) { - if (self === void 0) { - throw new ReferenceError( - "this hasn't been initialised - super() hasn't been called" - ); - } - return self; - } - - function _isNativeReflectConstruct() { - if (typeof Reflect === "undefined" || !Reflect.construct) - return false; - if (Reflect.construct.sham) return false; - if (typeof Proxy === "function") return true; - try { - Date.prototype.toString.call( - Reflect.construct(Date, [], function () {}) - ); - return true; - } catch (e) { - return false; - } - } - - function _getPrototypeOf(o) { - _getPrototypeOf = Object.setPrototypeOf - ? Object.getPrototypeOf - : function _getPrototypeOf(o) { - return o.__proto__ || Object.getPrototypeOf(o); - }; - return _getPrototypeOf(o); - } - - var Polling = __webpack_require__( - /*! ./polling */ "./node_modules/engine.io-client/lib/transports/polling.js" - ); - - var globalThis = __webpack_require__( - /*! ../globalThis */ "./node_modules/engine.io-client/lib/globalThis.browser.js" - ); - - var rNewline = /\n/g; - var rEscapedNewline = /\\n/g; - /** - * Global JSONP callbacks. - */ - - var callbacks; - - var JSONPPolling = /*#__PURE__*/ (function (_Polling) { - _inherits(JSONPPolling, _Polling); - - var _super = _createSuper(JSONPPolling); - - /** - * JSONP Polling constructor. - * - * @param {Object} opts. - * @api public - */ - function JSONPPolling(opts) { - var _this; - - _classCallCheck(this, JSONPPolling); - - _this = _super.call(this, opts); - _this.query = _this.query || {}; // define global callbacks array if not present - // we do this here (lazily) to avoid unneeded global pollution - - if (!callbacks) { - // we need to consider multiple engines in the same page - callbacks = globalThis.___eio = globalThis.___eio || []; - } // callback identifier - - _this.index = callbacks.length; // add callback to jsonp global - - var self = _assertThisInitialized(_this); - - callbacks.push(function (msg) { - self.onData(msg); - }); // append to query string - - _this.query.j = _this.index; - return _this; - } - /** - * JSONP only supports binary as base64 encoded strings - */ - - _createClass(JSONPPolling, [ - { - key: "doClose", - - /** - * Closes the socket. - * - * @api private - */ - value: function doClose() { - if (this.script) { - // prevent spurious errors from being emitted when the window is unloaded - this.script.onerror = function () {}; - - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - if (this.form) { - this.form.parentNode.removeChild(this.form); - this.form = null; - this.iframe = null; - } - - _get( - _getPrototypeOf(JSONPPolling.prototype), - "doClose", - this - ).call(this); - }, - /** - * Starts a poll cycle. - * - * @api private - */ - }, - { - key: "doPoll", - value: function doPoll() { - var self = this; - var script = document.createElement("script"); - - if (this.script) { - this.script.parentNode.removeChild(this.script); - this.script = null; - } - - script.async = true; - script.src = this.uri(); - - script.onerror = function (e) { - self.onError("jsonp poll error", e); - }; - - var insertAt = document.getElementsByTagName("script")[0]; - - if (insertAt) { - insertAt.parentNode.insertBefore(script, insertAt); - } else { - (document.head || document.body).appendChild(script); - } - - this.script = script; - var isUAgecko = - "undefined" !== typeof navigator && - /gecko/i.test(navigator.userAgent); - - if (isUAgecko) { - setTimeout(function () { - var iframe = document.createElement("iframe"); - document.body.appendChild(iframe); - document.body.removeChild(iframe); - }, 100); - } - }, - /** - * Writes with a hidden iframe. - * - * @param {String} data to send - * @param {Function} called upon flush. - * @api private - */ - }, - { - key: "doWrite", - value: function doWrite(data, fn) { - var self = this; - var iframe; - - if (!this.form) { - var form = document.createElement("form"); - var area = document.createElement("textarea"); - var id = (this.iframeId = "eio_iframe_" + this.index); - form.className = "socketio"; - form.style.position = "absolute"; - form.style.top = "-1000px"; - form.style.left = "-1000px"; - form.target = id; - form.method = "POST"; - form.setAttribute("accept-charset", "utf-8"); - area.name = "d"; - form.appendChild(area); - document.body.appendChild(form); - this.form = form; - this.area = area; - } - - this.form.action = this.uri(); - - function complete() { - initIframe(); - fn(); - } - - function initIframe() { - if (self.iframe) { - try { - self.form.removeChild(self.iframe); - } catch (e) { - self.onError("jsonp polling iframe removal error", e); - } - } - - try { - // ie6 dynamic iframes with target="" support (thanks Chris Lambacher) - var html = - '