mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-04-29 19:06:23 +02:00
feat: +unit test
fixbug: func has no value
This commit is contained in:
parent
5510df5f96
commit
6f039d004d
28 changed files with 367 additions and 552 deletions
|
|
@ -1,189 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/7/22 02:40
|
||||
# @Author : stellahong (stellahong@deepwisdom.ai)
|
||||
#
|
||||
from tests.metagpt.roles.ui_role import UIDesign
|
||||
|
||||
llm_resp = """
|
||||
# UI Design Description
|
||||
```The user interface for the snake game will be designed in a way that is simple, clean, and intuitive. The main elements of the game such as the game grid, snake, food, score, and game over message will be clearly defined and easy to understand. The game grid will be centered on the screen with the score displayed at the top. The game controls will be intuitive and easy to use. The design will be modern and minimalist with a pleasing color scheme.```
|
||||
|
||||
## Selected Elements
|
||||
|
||||
Game Grid: The game grid will be a rectangular area in the center of the screen where the game will take place. It will be defined by a border and will have a darker background color.
|
||||
|
||||
Snake: The snake will be represented by a series of connected blocks that move across the grid. The color of the snake will be different from the background color to make it stand out.
|
||||
|
||||
Food: The food will be represented by small objects that are a different color from the snake and the background. The food will be randomly placed on the grid.
|
||||
|
||||
Score: The score will be displayed at the top of the screen. The score will increase each time the snake eats a piece of food.
|
||||
|
||||
Game Over: When the game is over, a message will be displayed in the center of the screen. The player will be given the option to restart the game.
|
||||
|
||||
## HTML Layout
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Snake Game</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="score">Score: 0</div>
|
||||
<div class="game-grid">
|
||||
<!-- Snake and food will be dynamically generated here using JavaScript -->
|
||||
</div>
|
||||
<div class="game-over">Game Over</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## CSS Styles (styles.css)
|
||||
```css
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(20, 1fr);
|
||||
grid-template-rows: repeat(20, 1fr);
|
||||
gap: 1px;
|
||||
background-color: #222;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.snake-segment {
|
||||
background-color: #00cc66;
|
||||
}
|
||||
|
||||
.food {
|
||||
background-color: #cc3300;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 400px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
padding: 1em;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 3em;
|
||||
"""
|
||||
|
||||
|
||||
def test_ui_design_parse_css():
|
||||
ui_design_work = UIDesign(name="UI design action")
|
||||
|
||||
css = """
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(20, 1fr);
|
||||
grid-template-rows: repeat(20, 1fr);
|
||||
gap: 1px;
|
||||
background-color: #222;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.snake-segment {
|
||||
background-color: #00cc66;
|
||||
}
|
||||
|
||||
.food {
|
||||
background-color: #cc3300;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
width: 400px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
padding: 1em;
|
||||
font-size: 1em;
|
||||
border: none;
|
||||
background-color: #555;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.game-over {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 3em;
|
||||
"""
|
||||
assert ui_design_work.parse_css_code(context=llm_resp) == css
|
||||
|
||||
|
||||
def test_ui_design_parse_html():
|
||||
ui_design_work = UIDesign(name="UI design action")
|
||||
|
||||
html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Snake Game</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="score">Score: 0</div>
|
||||
<div class="game-grid">
|
||||
<!-- Snake and food will be dynamically generated here using JavaScript -->
|
||||
</div>
|
||||
<div class="game-over">Game Over</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
assert ui_design_work.parse_css_code(context=llm_resp) == html
|
||||
|
|
@ -7,30 +7,20 @@
|
|||
@Desc : Unit tests.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
from pydantic import BaseModel
|
||||
import pytest
|
||||
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.learn.text_to_embedding import text_to_embedding
|
||||
|
||||
|
||||
async def mock_text_to_embedding():
|
||||
class Input(BaseModel):
|
||||
input: str
|
||||
@pytest.mark.asyncio
|
||||
async def test_text_to_embedding():
|
||||
# Prerequisites
|
||||
assert CONFIG.OPENAI_API_KEY
|
||||
|
||||
inputs = [{"input": "Panda emoji"}]
|
||||
|
||||
for i in inputs:
|
||||
seed = Input(**i)
|
||||
v = await text_to_embedding(seed.input)
|
||||
assert len(v.data) > 0
|
||||
|
||||
|
||||
def test_suite():
|
||||
loop = asyncio.get_event_loop()
|
||||
task = loop.create_task(mock_text_to_embedding())
|
||||
loop.run_until_complete(task)
|
||||
v = await text_to_embedding(text="Panda emoji")
|
||||
assert len(v.data) > 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_suite()
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -24,9 +24,11 @@ async def test():
|
|||
assert "base64" in data or "http" in data
|
||||
key = CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL
|
||||
CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL = None
|
||||
data = await text_to_image("Panda emoji", size_type="512x512")
|
||||
assert "base64" in data or "http" in data
|
||||
CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL = key
|
||||
try:
|
||||
data = await text_to_image("Panda emoji", size_type="512x512")
|
||||
assert "base64" in data or "http" in data
|
||||
finally:
|
||||
CONFIG.METAGPT_TEXT_TO_IMAGE_MODEL_URL = key
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -29,9 +29,11 @@ async def test_text_to_speech():
|
|||
# test iflytek
|
||||
key = CONFIG.AZURE_TTS_SUBSCRIPTION_KEY
|
||||
CONFIG.AZURE_TTS_SUBSCRIPTION_KEY = ""
|
||||
data = await text_to_speech("panda emoji")
|
||||
assert "base64" in data or "http" in data
|
||||
CONFIG.AZURE_TTS_SUBSCRIPTION_KEY = key
|
||||
try:
|
||||
data = await text_to_speech("panda emoji")
|
||||
assert "base64" in data or "http" in data
|
||||
finally:
|
||||
CONFIG.AZURE_TTS_SUBSCRIPTION_KEY = key
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -58,6 +58,9 @@ async def test_memory_llm(llm):
|
|||
res = await memory.rewrite(sentence="apple Lily eating", context="", llm=llm)
|
||||
assert "Lily" in res
|
||||
|
||||
res = await memory.summarize(llm=llm)
|
||||
assert res
|
||||
|
||||
res = await memory.get_title(llm=llm)
|
||||
assert res
|
||||
assert "Lily" in res
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import UserRequirement
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.memory.longterm_memory import LongTermMemory
|
||||
|
|
@ -63,3 +65,7 @@ def test_ltm_search():
|
|||
assert len(news) == 1
|
||||
|
||||
ltm_new.clear()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
@Author : alexanderwu
|
||||
@File : mock_markdown.py
|
||||
"""
|
||||
import json
|
||||
|
||||
from metagpt.actions import UserRequirement, WriteDesign, WritePRD, WriteTasks
|
||||
from metagpt.schema import Message
|
||||
|
||||
|
|
@ -151,6 +153,32 @@ sequenceDiagram
|
|||
```
|
||||
"""
|
||||
|
||||
JSON_TASKS = {
|
||||
"Logic Analysis": """
|
||||
在这个项目中,所有的模块都依赖于“SearchEngine”类,这是主入口,其他的模块(Index、Ranking和Summary)都通过它交互。另外,"Index"类又依赖于"KnowledgeBase"类,因为它需要从知识库中获取数据。
|
||||
|
||||
- "main.py"包含"Main"类,是程序的入口点,它调用"SearchEngine"进行搜索操作,所以在其他任何模块之前,"SearchEngine"必须首先被定义。
|
||||
- "search.py"定义了"SearchEngine"类,它依赖于"Index"、"Ranking"和"Summary",因此,这些模块需要在"search.py"之前定义。
|
||||
- "index.py"定义了"Index"类,它从"knowledge_base.py"获取数据来创建索引,所以"knowledge_base.py"需要在"index.py"之前定义。
|
||||
- "ranking.py"和"summary.py"相对独立,只需确保在"search.py"之前定义。
|
||||
- "knowledge_base.py"是独立的模块,可以优先开发。
|
||||
- "interface.py"、"user_feedback.py"、"security.py"、"testing.py"和"monitoring.py"看起来像是功能辅助模块,可以在主要功能模块开发完成后并行开发。
|
||||
""",
|
||||
"Task list": [
|
||||
"smart_search_engine/knowledge_base.py",
|
||||
"smart_search_engine/index.py",
|
||||
"smart_search_engine/ranking.py",
|
||||
"smart_search_engine/summary.py",
|
||||
"smart_search_engine/search.py",
|
||||
"smart_search_engine/main.py",
|
||||
"smart_search_engine/interface.py",
|
||||
"smart_search_engine/user_feedback.py",
|
||||
"smart_search_engine/security.py",
|
||||
"smart_search_engine/testing.py",
|
||||
"smart_search_engine/monitoring.py",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
TASKS = """## Logic Analysis
|
||||
|
||||
|
|
@ -256,3 +284,4 @@ class MockMessages:
|
|||
prd = Message(role="Product Manager", content=PRD, cause_by=WritePRD)
|
||||
system_design = Message(role="Architect", content=SYSTEM_DESIGN, cause_by=WriteDesign)
|
||||
tasks = Message(role="Project Manager", content=TASKS, cause_by=WriteTasks)
|
||||
json_tasks = Message(role="Project Manager", content=json.dumps(JSON_TASKS), cause_by=WriteTasks)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
@File : test_asssistant.py
|
||||
@Desc : Used by AgentStore.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
|
@ -90,10 +91,42 @@ async def test_run():
|
|||
assert msg
|
||||
assert msg.cause_by == seed.cause_by
|
||||
assert msg.content
|
||||
# # Retrieve user terminal input.
|
||||
# logger.info("Enter prompt")
|
||||
# talk = input("You: ")
|
||||
# await role.talk(talk)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"memory",
|
||||
[
|
||||
{
|
||||
"history": [
|
||||
{
|
||||
"content": "can you draw me an picture?",
|
||||
"role": "user",
|
||||
"id": "1",
|
||||
},
|
||||
{"content": "Yes, of course. What do you want me to draw", "role": "assistant"},
|
||||
],
|
||||
"knowledge": [{"content": "tulin is a scientist."}],
|
||||
"last_talk": "Draw me an apple.",
|
||||
}
|
||||
],
|
||||
)
|
||||
@pytest.mark.asyncio
|
||||
async def test_memory(memory):
|
||||
role = Assistant()
|
||||
role.load_memory(memory)
|
||||
|
||||
val = role.get_memory()
|
||||
assert val
|
||||
|
||||
await role.talk("draw apple")
|
||||
|
||||
agent_skills = CONFIG.agent_skills
|
||||
CONFIG.agent_skills = []
|
||||
try:
|
||||
await role.think()
|
||||
finally:
|
||||
CONFIG.agent_skills = agent_skills
|
||||
assert isinstance(role.rc.todo, TalkAction)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -7,30 +7,51 @@
|
|||
@Modified By: mashenquan, 2023-11-1. In accordance with Chapter 2.2.1 and 2.2.2 of RFC 116, utilize the new message
|
||||
distribution feature for message handling.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import WriteCode, WriteTasks
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.const import (
|
||||
PRDS_FILE_REPO,
|
||||
REQUIREMENT_FILENAME,
|
||||
SYSTEM_DESIGN_FILE_REPO,
|
||||
TASK_FILE_REPO,
|
||||
)
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles.engineer import Engineer
|
||||
from metagpt.utils.common import CodeParser
|
||||
from metagpt.schema import CodingContext, Message
|
||||
from metagpt.utils.common import CodeParser, any_to_name, any_to_str, aread, awrite
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
from metagpt.utils.git_repository import ChangeType
|
||||
from tests.metagpt.roles.mock import STRS_FOR_PARSING, TASKS, MockMessages
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_engineer():
|
||||
engineer = Engineer()
|
||||
# Prerequisites
|
||||
rqno = "20231221155954.json"
|
||||
await FileRepository.save_file(REQUIREMENT_FILENAME, content=MockMessages.req.content)
|
||||
await FileRepository.save_file(rqno, relative_path=PRDS_FILE_REPO, content=MockMessages.prd.content)
|
||||
await FileRepository.save_file(
|
||||
rqno, relative_path=SYSTEM_DESIGN_FILE_REPO, content=MockMessages.system_design.content
|
||||
)
|
||||
await FileRepository.save_file(rqno, relative_path=TASK_FILE_REPO, content=MockMessages.json_tasks.content)
|
||||
|
||||
engineer.put_message(MockMessages.req)
|
||||
engineer.put_message(MockMessages.prd)
|
||||
engineer.put_message(MockMessages.system_design)
|
||||
rsp = await engineer.run(MockMessages.tasks)
|
||||
engineer = Engineer()
|
||||
rsp = await engineer.run(Message(content="", cause_by=WriteTasks))
|
||||
|
||||
logger.info(rsp)
|
||||
assert "all done." == rsp.content
|
||||
assert rsp.cause_by == any_to_str(WriteCode)
|
||||
src_file_repo = CONFIG.git_repo.new_file_repository(CONFIG.src_workspace)
|
||||
assert src_file_repo.changed_files
|
||||
|
||||
|
||||
def test_parse_str():
|
||||
for idx, i in enumerate(STRS_FOR_PARSING):
|
||||
text = CodeParser.parse_str(f"{idx+1}", i)
|
||||
text = CodeParser.parse_str(f"{idx + 1}", i)
|
||||
# logger.info(text)
|
||||
assert text == "a"
|
||||
|
||||
|
|
@ -84,3 +105,59 @@ def test_parse_code():
|
|||
logger.info(code)
|
||||
assert isinstance(code, str)
|
||||
assert target_code == code
|
||||
|
||||
|
||||
def test_todo():
|
||||
role = Engineer()
|
||||
assert role.todo == any_to_name(WriteCode)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_new_coding_context():
|
||||
# Prerequisites
|
||||
demo_path = Path(__file__).parent / "../../data/demo_project"
|
||||
deps = json.loads(await aread(demo_path / "dependencies.json"))
|
||||
dependency = await CONFIG.git_repo.get_dependency()
|
||||
for k, v in deps.items():
|
||||
await dependency.update(k, set(v))
|
||||
data = await aread(demo_path / "system_design.json")
|
||||
rqno = "20231221155954.json"
|
||||
await awrite(CONFIG.git_repo.workdir / SYSTEM_DESIGN_FILE_REPO / rqno, data)
|
||||
data = await aread(demo_path / "tasks.json")
|
||||
await awrite(CONFIG.git_repo.workdir / TASK_FILE_REPO / rqno, data)
|
||||
|
||||
CONFIG.src_workspace = Path(CONFIG.git_repo.workdir) / "game_2048"
|
||||
src_file_repo = CONFIG.git_repo.new_file_repository(relative_path=CONFIG.src_workspace)
|
||||
task_file_repo = CONFIG.git_repo.new_file_repository(relative_path=TASK_FILE_REPO)
|
||||
design_file_repo = CONFIG.git_repo.new_file_repository(relative_path=SYSTEM_DESIGN_FILE_REPO)
|
||||
|
||||
filename = "game.py"
|
||||
ctx_doc = await Engineer._new_coding_doc(
|
||||
filename=filename,
|
||||
src_file_repo=src_file_repo,
|
||||
task_file_repo=task_file_repo,
|
||||
design_file_repo=design_file_repo,
|
||||
dependency=dependency,
|
||||
)
|
||||
assert ctx_doc
|
||||
assert ctx_doc.filename == filename
|
||||
assert ctx_doc.content
|
||||
ctx = CodingContext.model_validate_json(ctx_doc.content)
|
||||
assert ctx.filename == filename
|
||||
assert ctx.design_doc
|
||||
assert ctx.design_doc.content
|
||||
assert ctx.task_doc
|
||||
assert ctx.task_doc.content
|
||||
assert ctx.code_doc
|
||||
|
||||
CONFIG.git_repo.add_change({f"{TASK_FILE_REPO}/{rqno}": ChangeType.UNTRACTED})
|
||||
CONFIG.git_repo.commit("mock env")
|
||||
await src_file_repo.save(filename=filename, content="content")
|
||||
role = Engineer()
|
||||
assert not role.code_todos
|
||||
await role._new_code_actions()
|
||||
assert role.code_todos
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -48,3 +48,7 @@ def test_write_report(mocker):
|
|||
content = "# Research Report"
|
||||
researcher.Researcher().write_report(topic, content)
|
||||
assert (researcher.RESEARCH_PATH / f"{i+1}. metagpt.md").read_text().startswith("# Research Report")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# @Desc : unittest of Role
|
||||
import pytest
|
||||
|
||||
from metagpt.roles.role import Role
|
||||
|
||||
|
|
@ -9,3 +10,7 @@ def test_role_desc():
|
|||
role = Role(profile="Sales", desc="Best Seller")
|
||||
assert role.profile == "Sales"
|
||||
assert role.desc == "Best Seller"
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/7/22 02:40
|
||||
# @Author : stellahong (stellahong@deepwisdom.ai)
|
||||
#
|
||||
from metagpt.roles import ProductManager
|
||||
from metagpt.team import Team
|
||||
from tests.metagpt.roles.ui_role import UI
|
||||
|
||||
|
||||
def test_add_ui():
|
||||
ui = UI()
|
||||
assert ui.profile == "UI Design"
|
||||
|
||||
|
||||
async def test_ui_role(idea: str, investment: float = 3.0, n_round: int = 5):
|
||||
"""Run a startup. Be a boss."""
|
||||
company = Team()
|
||||
company.hire([ProductManager(), UI()])
|
||||
company.invest(investment)
|
||||
company.run_project(idea)
|
||||
await company.run(n_round=n_round)
|
||||
|
|
@ -1,280 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/7/15 16:40
|
||||
# @Author : stellahong (stellahong@deepwisdom.ai)
|
||||
# @Desc :
|
||||
import os
|
||||
import re
|
||||
from functools import wraps
|
||||
from importlib import import_module
|
||||
|
||||
from metagpt.actions import Action, ActionOutput, WritePRD
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.logs import logger
|
||||
from metagpt.roles import Role
|
||||
from metagpt.schema import Message
|
||||
from metagpt.tools.sd_engine import SDEngine
|
||||
|
||||
PROMPT_TEMPLATE = """
|
||||
{context}
|
||||
|
||||
## Role
|
||||
You are a UserInterface Designer; the goal is to finish a UI design according to PRD, give a design description, and select specified elements and UI style.
|
||||
"""
|
||||
|
||||
UI_DESIGN_DESC = ActionNode(
|
||||
key="UI Design Desc",
|
||||
expected_type=str,
|
||||
instruction="place the design objective here",
|
||||
example="Snake games are classic and addictive games with simple yet engaging elements. Here are the main elements"
|
||||
" commonly found in snake games",
|
||||
)
|
||||
|
||||
SELECTED_ELEMENTS = ActionNode(
|
||||
key="Selected Elements",
|
||||
expected_type=list[str],
|
||||
instruction="up to 5 specified elements, clear and simple",
|
||||
example=[
|
||||
"Game Grid: The game grid is a rectangular...",
|
||||
"Snake: The player controls a snake that moves across the grid...",
|
||||
"Food: Food items (often represented as small objects or differently colored blocks)",
|
||||
"Score: The player's score increases each time the snake eats a piece of food. The longer the snake becomes, the higher the score.",
|
||||
"Game Over: The game ends when the snake collides with itself or an obstacle. At this point, the player's final score is displayed, and they are given the option to restart the game.",
|
||||
],
|
||||
)
|
||||
|
||||
HTML_LAYOUT = ActionNode(
|
||||
key="HTML Layout",
|
||||
expected_type=str,
|
||||
instruction="use standard HTML code",
|
||||
example="""<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Snake Game</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="game-grid">
|
||||
<!-- Snake will be dynamically generated here using JavaScript -->
|
||||
</div>
|
||||
<div class="food">
|
||||
<!-- Food will be dynamically generated here using JavaScript -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
""",
|
||||
)
|
||||
|
||||
CSS_STYLES = ActionNode(
|
||||
key="CSS Styles",
|
||||
expected_type=str,
|
||||
instruction="use standard css code",
|
||||
example="""body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.game-grid {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(20, 1fr); /* Adjust to the desired grid size */
|
||||
grid-template-rows: repeat(20, 1fr);
|
||||
gap: 1px;
|
||||
background-color: #222;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.game-grid div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #444;
|
||||
}
|
||||
|
||||
.snake-segment {
|
||||
background-color: #00cc66; /* Snake color */
|
||||
}
|
||||
|
||||
.food {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #cc3300; /* Food color */
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* Optional styles for a simple game over message */
|
||||
.game-over {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #ff0000;
|
||||
display: none;
|
||||
}
|
||||
""",
|
||||
)
|
||||
|
||||
ANYTHING_UNCLEAR = ActionNode(
|
||||
key="Anything UNCLEAR",
|
||||
expected_type=str,
|
||||
instruction="Mention any aspects of the project that are unclear and try to clarify them.",
|
||||
example="...",
|
||||
)
|
||||
|
||||
NODES = [
|
||||
UI_DESIGN_DESC,
|
||||
SELECTED_ELEMENTS,
|
||||
HTML_LAYOUT,
|
||||
CSS_STYLES,
|
||||
ANYTHING_UNCLEAR,
|
||||
]
|
||||
|
||||
UI_DESIGN_NODE = ActionNode.from_children("UI_DESIGN", NODES)
|
||||
|
||||
|
||||
def load_engine(func):
|
||||
"""Decorator to load an engine by file name and engine name."""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
file_name, engine_name = func(*args, **kwargs)
|
||||
engine_file = import_module(file_name, package="metagpt")
|
||||
ip_module_cls = getattr(engine_file, engine_name)
|
||||
try:
|
||||
engine = ip_module_cls()
|
||||
except:
|
||||
engine = None
|
||||
|
||||
return engine
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def parse(func):
|
||||
"""Decorator to parse information using regex pattern."""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
context, pattern = func(*args, **kwargs)
|
||||
match = re.search(pattern, context, re.DOTALL)
|
||||
if match:
|
||||
text_info = match.group(1)
|
||||
logger.info(text_info)
|
||||
else:
|
||||
text_info = context
|
||||
logger.info("未找到匹配的内容")
|
||||
|
||||
return text_info
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class UIDesign(Action):
|
||||
"""Class representing the UI Design action."""
|
||||
|
||||
def __init__(self, name, context=None, llm=None):
|
||||
super().__init__(name, context, llm) # 需要调用LLM进一步丰富UI设计的prompt
|
||||
|
||||
@parse
|
||||
def parse_requirement(self, context: str):
|
||||
"""Parse UI Design draft from the context using regex."""
|
||||
pattern = r"## UI Design draft.*?\n(.*?)## Anything UNCLEAR"
|
||||
return context, pattern
|
||||
|
||||
@parse
|
||||
def parse_ui_elements(self, context: str):
|
||||
"""Parse Selected Elements from the context using regex."""
|
||||
pattern = r"## Selected Elements.*?\n(.*?)## HTML Layout"
|
||||
return context, pattern
|
||||
|
||||
@parse
|
||||
def parse_css_code(self, context: str):
|
||||
pattern = r"```css.*?\n(.*?)## Anything UNCLEAR"
|
||||
return context, pattern
|
||||
|
||||
@parse
|
||||
def parse_html_code(self, context: str):
|
||||
pattern = r"```html.*?\n(.*?)```"
|
||||
return context, pattern
|
||||
|
||||
async def draw_icons(self, context, *args, **kwargs):
|
||||
"""Draw icons using SDEngine."""
|
||||
engine = SDEngine()
|
||||
icon_prompts = self.parse_ui_elements(context)
|
||||
icons = icon_prompts.split("\n")
|
||||
icons = [s for s in icons if len(s.strip()) > 0]
|
||||
prompts_batch = []
|
||||
for icon_prompt in icons:
|
||||
# fixme: 添加icon lora
|
||||
prompt = engine.construct_payload(icon_prompt + ".<lora:WZ0710_AW81e-3_30e3b128d64T32_goon0.5>")
|
||||
prompts_batch.append(prompt)
|
||||
await engine.run_t2i(prompts_batch)
|
||||
logger.info("Finish icon design using StableDiffusion API")
|
||||
|
||||
async def _save(self, css_content, html_content):
|
||||
save_dir = CONFIG.workspace_path / "resources" / "codes"
|
||||
if not os.path.exists(save_dir):
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
# Save CSS and HTML content to files
|
||||
css_file_path = save_dir / "ui_design.css"
|
||||
html_file_path = save_dir / "ui_design.html"
|
||||
|
||||
css_file_path.write_text(css_content)
|
||||
html_file_path.write_text(html_content)
|
||||
|
||||
async def run(self, requirements: list[Message], *args, **kwargs) -> ActionOutput:
|
||||
"""Run the UI Design action."""
|
||||
# fixme: update prompt (根据需求细化prompt)
|
||||
context = requirements[-1].content
|
||||
ui_design_draft = self.parse_requirement(context=context)
|
||||
# todo: parse requirements str
|
||||
prompt = PROMPT_TEMPLATE.format(context=ui_design_draft)
|
||||
logger.info(prompt)
|
||||
ui_describe = await UI_DESIGN_NODE.fill(prompt)
|
||||
logger.info(ui_describe.content)
|
||||
logger.info(ui_describe.instruct_content)
|
||||
css = self.parse_css_code(context=ui_describe.content)
|
||||
html = self.parse_html_code(context=ui_describe.content)
|
||||
await self._save(css_content=css, html_content=html)
|
||||
await self.draw_icons(ui_describe.content)
|
||||
return ui_describe
|
||||
|
||||
|
||||
class UI(Role):
|
||||
"""Class representing the UI Role."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name="Catherine",
|
||||
profile="UI Design",
|
||||
goal="Finish a workable and good User Interface design based on a product design",
|
||||
constraints="Give clear layout description and use standard icons to finish the design",
|
||||
skills=["SD"],
|
||||
):
|
||||
super().__init__(name, profile, goal, constraints)
|
||||
self.load_skills(skills)
|
||||
self._init_actions([UIDesign])
|
||||
self._watch([WritePRD])
|
||||
|
||||
@load_engine
|
||||
def load_sd_engine(self):
|
||||
"""Load the SDEngine."""
|
||||
file_name = ".tools.sd_engine"
|
||||
engine_name = "SDEngine"
|
||||
return file_name, engine_name
|
||||
|
||||
def load_skills(self, skills):
|
||||
"""Load skills for the UI Role."""
|
||||
# todo: 添加其他出图engine
|
||||
for skill in skills:
|
||||
if skill == "SD":
|
||||
self.sd_engine = self.load_sd_engine()
|
||||
logger.info(f"load skill engine {self.sd_engine}")
|
||||
|
|
@ -10,10 +10,20 @@
|
|||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.actions import Action
|
||||
from metagpt.actions.action_node import ActionNode
|
||||
from metagpt.actions.write_code import WriteCode
|
||||
from metagpt.schema import AIMessage, Message, SystemMessage, UserMessage
|
||||
from metagpt.config import CONFIG
|
||||
from metagpt.schema import (
|
||||
AIMessage,
|
||||
Document,
|
||||
Message,
|
||||
MessageQueue,
|
||||
SystemMessage,
|
||||
UserMessage,
|
||||
)
|
||||
from metagpt.utils.common import any_to_str
|
||||
|
||||
|
||||
|
|
@ -95,3 +105,32 @@ def test_message_serdeser():
|
|||
new_message = Message(**message_dict)
|
||||
assert new_message.instruct_content is None
|
||||
assert new_message.cause_by == "metagpt.actions.add_requirement.UserRequirement"
|
||||
assert not Message.load("{")
|
||||
|
||||
|
||||
def test_document():
|
||||
doc = Document(root_path="a", filename="b", content="c")
|
||||
meta_doc = doc.get_meta()
|
||||
assert doc.root_path == meta_doc.root_path
|
||||
assert doc.filename == meta_doc.filename
|
||||
assert meta_doc.content == ""
|
||||
|
||||
assert doc.full_path == str(CONFIG.git_repo.workdir / doc.root_path / doc.filename)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_message_queue():
|
||||
mq = MessageQueue()
|
||||
mq.push(Message(content="1"))
|
||||
mq.push(Message(content="2中文测试aaa"))
|
||||
msg = mq.pop()
|
||||
assert msg.content == "1"
|
||||
|
||||
val = await mq.dump()
|
||||
assert val
|
||||
new_mq = MessageQueue.load(val)
|
||||
assert new_mq.pop_all() == mq.pop_all()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
|
|
|
|||
|
|
@ -53,7 +53,8 @@ async def test_dependency_file():
|
|||
|
||||
file1 = DependencyFile(workdir=Path(__file__).parent)
|
||||
assert file1.exists
|
||||
assert await file1.get("a/b.txt") == set()
|
||||
assert await file1.get("a/b.txt", persist=False) == set()
|
||||
assert await file1.get("a/b.txt") == {"c/e.txt", "d.txt"}
|
||||
await file1.load()
|
||||
assert await file1.get("a/b.txt") == {"c/e.txt", "d.txt"}
|
||||
file1.delete_file()
|
||||
|
|
|
|||
|
|
@ -15,7 +15,13 @@ from metagpt.utils.file import File
|
|||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
("root_path", "filename", "content"),
|
||||
[(Path("/code/MetaGPT/data/tutorial_docx/2023-09-07_17-05-20"), "test.md", "Hello World!")],
|
||||
[
|
||||
(
|
||||
Path(__file__).parent / "../../../workspace/unittest/data/tutorial_docx/2023-09-07_17-05-20",
|
||||
"test.md",
|
||||
"Hello World!",
|
||||
)
|
||||
],
|
||||
)
|
||||
async def test_write_and_read_file(root_path: Path, filename: str, content: bytes):
|
||||
full_file_name = await File.write(root_path=root_path, filename=filename, content=content.encode("utf-8"))
|
||||
|
|
|
|||
|
|
@ -47,9 +47,11 @@ async def test_s3_no_error():
|
|||
conn = S3()
|
||||
key = conn.auth_config["aws_secret_access_key"]
|
||||
conn.auth_config["aws_secret_access_key"] = ""
|
||||
res = await conn.cache("ABC", ".bak", "script")
|
||||
assert not res
|
||||
conn.auth_config["aws_secret_access_key"] = key
|
||||
try:
|
||||
res = await conn.cache("ABC", ".bak", "script")
|
||||
assert not res
|
||||
finally:
|
||||
conn.auth_config["aws_secret_access_key"] = key
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue