diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py index 021edfe72..f987c6042 100644 --- a/metagpt/actions/design_api.py +++ b/metagpt/actions/design_api.py @@ -24,6 +24,7 @@ from metagpt.const import ( from metagpt.logs import logger from metagpt.schema import Document, Documents from metagpt.utils.common import CodeParser +from metagpt.utils.file_repository import FileRepository from metagpt.utils.get_template import get_template from metagpt.utils.mermaid import mermaid_to_file @@ -304,8 +305,7 @@ class WriteDesign(Action): @staticmethod async def _save_pdf(design_doc): - file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_PDF_FILE_REPO) - await file_repo.save_pdf(doc=design_doc) + await FileRepository.save_as(doc=design_doc, with_suffix=".md", relative_path=SYSTEM_DESIGN_PDF_FILE_REPO) @staticmethod async def _save_mermaid_file(data: str, pathname: Path): diff --git a/metagpt/actions/prepare_documents.py b/metagpt/actions/prepare_documents.py index 92d5730b2..30558c93f 100644 --- a/metagpt/actions/prepare_documents.py +++ b/metagpt/actions/prepare_documents.py @@ -29,7 +29,7 @@ class PrepareDocuments(Action): # Create and initialize the workspace folder, initialize the Git environment. CONFIG.git_repo = GitRepository() - workdir = Path(CONFIG.WORKDIR) if CONFIG.WORKDIR else WORKSPACE_ROOT / FileRepository.new_file_name() + workdir = Path(CONFIG.WORKDIR) if CONFIG.WORKDIR else WORKSPACE_ROOT / FileRepository.new_filename() CONFIG.git_repo.open(local_path=workdir, auto_init=True) # Write the newly added requirements from the main parameter idea to `docs/requirement.txt`. diff --git a/metagpt/actions/project_management.py b/metagpt/actions/project_management.py index d679a730c..7205d11e7 100644 --- a/metagpt/actions/project_management.py +++ b/metagpt/actions/project_management.py @@ -18,6 +18,7 @@ from metagpt.config import CONFIG from metagpt.const import SYSTEM_DESIGN_FILE_REPO, TASK_FILE_REPO, TASK_PDF_FILE_REPO, PACKAGE_REQUIREMENTS_FILENAME from metagpt.logs import logger from metagpt.schema import Document, Documents +from metagpt.utils.file_repository import FileRepository from metagpt.utils.get_template import get_template templates = { @@ -275,8 +276,7 @@ class WriteTasks(Action): @staticmethod async def _save_pdf(task_doc): - file_repo = CONFIG.git_repo.new_file_repository(TASK_PDF_FILE_REPO) - await file_repo.save_pdf(doc=task_doc) + await FileRepository.save_as(doc=task_doc, with_suffix=".md", relative_path=TASK_PDF_FILE_REPO) class AssignTasks(Action): diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index e9d41bb20..3a4ca7768 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -23,6 +23,7 @@ from metagpt.const import TEST_OUTPUTS_FILE_REPO from metagpt.logs import logger from metagpt.schema import CodingContext, RunCodeResult from metagpt.utils.common import CodeParser +from metagpt.utils.file_repository import FileRepository PROMPT_TEMPLATE = """ NOTICE @@ -82,9 +83,8 @@ class WriteCode(Action): async def run(self, *args, **kwargs) -> CodingContext: coding_context = CodingContext.loads(self.context.content) - test_doc = await CONFIG.git_repo.new_file_repository(TEST_OUTPUTS_FILE_REPO).get( - "test_" + coding_context.filename + ".json" - ) + test_doc = await FileRepository.get_file(filename="test_" + coding_context.filename + ".json", + relative_path=TEST_OUTPUTS_FILE_REPO) logs = "" if test_doc: test_detail = RunCodeResult.loads(test_doc.content) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index cc21058b4..c1653a850 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -290,8 +290,7 @@ class WritePRD(Action): async def run(self, with_messages, format=CONFIG.prompt_format, *args, **kwargs) -> ActionOutput: # Determine which requirement documents need to be rewritten: Use LLM to assess whether new requirements are # related to the PRD. If they are related, rewrite the PRD. - docs_file_repo = CONFIG.git_repo.new_file_repository(DOCS_FILE_REPO) - requirement_doc = await docs_file_repo.get(REQUIREMENT_FILENAME) + requirement_doc = await FileRepository.get_file(filename=REQUIREMENT_FILENAME, relative_path=DOCS_FILE_REPO) prds_file_repo = CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO) prd_docs = await prds_file_repo.get_all() change_files = Documents() @@ -355,7 +354,7 @@ class WritePRD(Action): prd = await self._run_new_requirement(requirements=[requirement_doc.content], *args, **kwargs) new_prd_doc = Document( root_path=PRDS_FILE_REPO, - filename=FileRepository.new_file_name() + ".json", + filename=FileRepository.new_filename() + ".json", content=prd.instruct_content.json(), ) elif await self._is_relative_to(requirement_doc, prd_doc): @@ -382,5 +381,4 @@ class WritePRD(Action): @staticmethod async def _save_pdf(prd_doc): - file_repo = CONFIG.git_repo.new_file_repository(PRD_PDF_FILE_REPO) - await file_repo.save_pdf(doc=prd_doc) + await FileRepository.save_as(doc=prd_doc, with_suffix=".md", relative_path=PRD_PDF_FILE_REPO) diff --git a/metagpt/utils/file_repository.py b/metagpt/utils/file_repository.py index 3df53cca3..018cac168 100644 --- a/metagpt/utils/file_repository.py +++ b/metagpt/utils/file_repository.py @@ -168,7 +168,7 @@ class FileRepository: return children @staticmethod - def new_file_name(): + def new_filename(): """Generate a new filename based on the current timestamp and a UUID suffix. :return: A new filename string. @@ -178,14 +178,22 @@ class FileRepository: # guid_suffix = str(uuid.uuid4())[:8] # return f"{current_time}x{guid_suffix}" - async def save_pdf(self, doc: Document): - """Save a Document as a PDF file. + async def save_doc(self, doc: Document, with_suffix:str = None, dependencies: List[str] = None): + """Save a Document instance as a PDF file. + + This method converts the content of the Document instance to Markdown, + saves it to a file with an optional specified suffix, and logs the saved file. :param doc: The Document instance to be saved. + :type doc: Document + :param with_suffix: An optional suffix to append to the saved file's name. + :type with_suffix: str, optional + :param dependencies: A list of dependencies for the saved file. + :type dependencies: List[str], optional """ m = json.loads(doc.content) - filename = Path(doc.filename).with_suffix(".md") - await self.save(filename=str(filename), content=json_to_markdown(m)) + filename = Path(doc.filename).with_suffix(with_suffix) if with_suffix is not None else Path(doc.filename) + await self.save(filename=str(filename), content=json_to_markdown(m), dependencies=dependencies) logger.info(f"File Saved: {str(filename)}") @staticmethod @@ -228,3 +236,24 @@ class FileRepository: """ file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path) return await file_repo.save(filename=filename, content=content, dependencies=dependencies) + + @staticmethod + async def save_as(doc:Document, with_suffix:str = None, dependencies: List[str] = None, relative_path: Path | str = "."): + """Save a Document instance with optional modifications. + + This static method creates a new FileRepository, saves the Document instance + with optional modifications (such as a suffix), and logs the saved file. + + :param doc: The Document instance to be saved. + :type doc: Document + :param with_suffix: An optional suffix to append to the saved file's name. + :type with_suffix: str, optional + :param dependencies: A list of dependencies for the saved file. + :type dependencies: List[str], optional + :param relative_path: The relative path within the file repository. + :type relative_path: Path or str, optional + :return: A boolean indicating whether the save operation was successful. + :rtype: bool + """ + file_repo = CONFIG.git_repo.new_file_repository(relative_path=relative_path) + return await file_repo.save_doc(doc=doc, with_suffix=with_suffix, dependencies=dependencies)