diff --git a/examples/ui_with_chainlit/.gitignore b/examples/ui_with_chainlit/.gitignore new file mode 100644 index 000000000..1e528c384 --- /dev/null +++ b/examples/ui_with_chainlit/.gitignore @@ -0,0 +1,3 @@ +*.chainlit +chainlit.md +.files \ No newline at end of file diff --git a/examples/ui_with_chainlit/README.md b/examples/ui_with_chainlit/README.md new file mode 100644 index 000000000..0ad466162 --- /dev/null +++ b/examples/ui_with_chainlit/README.md @@ -0,0 +1,34 @@ +# MetaGPT in UI with Chainlit! 🤖 + +- MetaGPT functionality in UI using Chainlit. +- It also takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.**, But `everything in UI`. + +## Install Chainlit + +- Setup initial MetaGPT config from [Main](../../README.md). + +```bash +pip install chainlit +``` + +## Usage + +```bash +chainlit run app.py +``` + +- Now go to: http://localhost:8000 + +- Select, + - `Create a 2048 game` + - `Write a cli Blackjack Game` + - `Type your own message...` + +- It will run a metagpt software company. + +## To Setup with own application + +- We can change `Environment.run`, `Team.run`, `Role.run`, `Role._act`, `Action.run`. +- In this code, changed `Environment.run`, as it was easier to do. +- We will need to change `metagpt.logs.set_llm_stream_logfunc` to stream messages in UI with Chainlit Message. +- To use at some other place we need to call `chainlit.Message(content="").send()` with content. \ No newline at end of file diff --git a/examples/ui_with_chainlit/__init__.py b/examples/ui_with_chainlit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/examples/ui_with_chainlit/app.py b/examples/ui_with_chainlit/app.py new file mode 100644 index 000000000..3b449a12c --- /dev/null +++ b/examples/ui_with_chainlit/app.py @@ -0,0 +1,83 @@ +import chainlit as cl +from init_setup import ChainlitEnv + +from metagpt.roles import ( + Architect, + Engineer, + ProductManager, + ProjectManager, + QaEngineer, +) +from metagpt.team import Team + + +# https://docs.chainlit.io/concepts/starters +@cl.set_chat_profiles +async def chat_profile() -> list[cl.ChatProfile]: + """Generates a chat profile containing starter messages which can be triggered to run MetaGPT + + Returns: + list[chainlit.ChatProfile]: List of Chat Profile + """ + return [ + cl.ChatProfile( + name="MetaGPT", + icon="/public/MetaGPT-new-log.jpg", + markdown_description="It takes a **one line requirement** as input and outputs **user stories / competitive analysis / requirements / data structures / APIs / documents, etc.**, But `everything in UI`.", + starters=[ + cl.Starter( + label="Create a 2048 Game", + message="Create a 2048 game", + icon="/public/2048.jpg", + ), + cl.Starter( + label="Write a cli Blackjack Game", + message="Write a cli Blackjack Game", + icon="/public/blackjack.jpg", + ), + ], + ) + ] + + +# https://docs.chainlit.io/concepts/message +@cl.on_message +async def startup(message: cl.Message) -> None: + """On Message in UI, Create a MetaGPT software company + + Args: + message (chainlit.Message): message by chainlist + """ + idea = message.content + company = Team(env=ChainlitEnv()) + + # Similar to software_company.py + company.hire( + [ + ProductManager(), + Architect(), + ProjectManager(), + Engineer(n_borg=5, use_code_review=True), + QaEngineer(), + ] + ) + + company.invest(investment=3.0) + company.run_project(idea=idea) + + await company.run(n_round=5) + + workdir = company.env.context.git_repo.workdir + files = company.env.context.git_repo.get_files(workdir) + files = "\n".join([f"{workdir}/{file}" for file in files if not file.startswith(".git")]) + + await cl.Message( + content=f""" +Codes can be found here: +{files} + +--- + +Total cost: `{company.cost_manager.total_cost}` +""" + ).send() diff --git a/examples/ui_with_chainlit/init_setup.py b/examples/ui_with_chainlit/init_setup.py new file mode 100644 index 000000000..2b00fe465 --- /dev/null +++ b/examples/ui_with_chainlit/init_setup.py @@ -0,0 +1,69 @@ +import asyncio + +import chainlit as cl + +from metagpt.environment import Environment +from metagpt.logs import logger, set_llm_stream_logfunc +from metagpt.roles import Role +from metagpt.utils.common import any_to_name + + +def log_llm_stream_chainlit(msg): + # Stream the message token into Chainlit UI. + cl.run_sync(chainlit_message.stream_token(msg)) + + +set_llm_stream_logfunc(func=log_llm_stream_chainlit) + + +class ChainlitEnv(Environment): + """Chainlit Environment for UI Integration""" + + async def run(self, k=1): + """处理一次所有信息的运行 + Process all Role runs at once + """ + for _ in range(k): + futures = [] + for role in self.roles.values(): + # Call role.run with chainlit configuration + future = self._chainlit_role_run(role=role) + futures.append(future) + + await asyncio.gather(*futures) + logger.debug(f"is idle: {self.is_idle}") + + async def _chainlit_role_run(self, role: Role) -> None: + """To run the role with chainlit config + + Args: + role (Role): metagpt.role.Role + """ + global chainlit_message + chainlit_message = cl.Message(content="") + + message = await role.run() + # If message is from role._act() publish to UI. + if message is not None and message.content != "No actions taken yet": + # Convert a message from action node in json format + chainlit_message.content = await self._convert_message_to_markdownjson(message=chainlit_message.content) + + # message content from which role and its action... + chainlit_message.content += f"---\n\nAction: `{any_to_name(message.cause_by)}` done by `{role._setting}`." + + await chainlit_message.send() + + # for clean view in UI + async def _convert_message_to_markdownjson(self, message: str) -> str: + """If the message is from MetaGPT Action Node output, then + convert it into markdown json for clear view in UI. + + Args: + message (str): message by role._act + + Returns: + str: message in mardown from + """ + if message.startswith("[CONTENT]"): + return f"```json\n{message}\n```\n" + return message diff --git a/examples/ui_with_chainlit/public/2048.jpg b/examples/ui_with_chainlit/public/2048.jpg new file mode 100644 index 000000000..7042e6f63 Binary files /dev/null and b/examples/ui_with_chainlit/public/2048.jpg differ diff --git a/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg b/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg new file mode 100644 index 000000000..f67872008 Binary files /dev/null and b/examples/ui_with_chainlit/public/MetaGPT-new-log.jpg differ diff --git a/examples/ui_with_chainlit/public/blackjack.jpg b/examples/ui_with_chainlit/public/blackjack.jpg new file mode 100644 index 000000000..b3a412bd4 Binary files /dev/null and b/examples/ui_with_chainlit/public/blackjack.jpg differ