You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
356 lines
11 KiB
356 lines
11 KiB
import json
|
|
import os
|
|
import subprocess
|
|
import time
|
|
from datetime import datetime
|
|
from typing import Any, Dict, List, Union
|
|
|
|
from pydantic import BaseModel, Field
|
|
from pydantic.v1 import validator
|
|
|
|
from swarms.utils.file_processing import create_file_in_folder
|
|
from swarms.utils.loguru_logger import initialize_logger
|
|
|
|
logger = initialize_logger(log_folder="main_artifact")
|
|
|
|
|
|
class FileVersion(BaseModel):
|
|
"""
|
|
Represents a version of the file with its content and timestamp.
|
|
"""
|
|
|
|
version_number: int = Field(
|
|
..., description="The version number of the file"
|
|
)
|
|
content: str = Field(
|
|
..., description="The content of the file version"
|
|
)
|
|
timestamp: str = Field(
|
|
time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
description="The timestamp of the file version",
|
|
)
|
|
|
|
def __str__(self) -> str:
|
|
return f"Version {self.version_number} (Timestamp: {self.timestamp}):\n{self.content}"
|
|
|
|
|
|
class Artifact(BaseModel):
|
|
"""
|
|
Represents a file artifact.
|
|
|
|
Attributes:
|
|
folder_path
|
|
file_path (str): The path to the file.
|
|
file_type (str): The type of the file.
|
|
contents (str): The contents of the file.
|
|
versions (List[FileVersion]): The list of file versions.
|
|
edit_count (int): The number of times the file has been edited.
|
|
"""
|
|
|
|
folder_path: str = Field(
|
|
default=os.getenv("WORKSPACE_DIR"),
|
|
description="The path to the folder",
|
|
)
|
|
file_path: str = Field(..., description="The path to the file")
|
|
file_type: str = Field(
|
|
...,
|
|
description="The type of the file",
|
|
# example=".txt",
|
|
)
|
|
contents: str = Field(
|
|
..., description="The contents of the file in string format"
|
|
)
|
|
versions: List[FileVersion] = Field(default_factory=list)
|
|
edit_count: int = Field(
|
|
...,
|
|
description="The number of times the file has been edited",
|
|
)
|
|
|
|
@validator("file_type", pre=True, always=True)
|
|
def validate_file_type(cls, v, values):
|
|
if not v:
|
|
file_path = values.get("file_path")
|
|
_, ext = os.path.splitext(file_path)
|
|
if ext.lower() not in [
|
|
".py",
|
|
".csv",
|
|
".tsv",
|
|
".txt",
|
|
".json",
|
|
".xml",
|
|
".html",
|
|
".yaml",
|
|
".yml",
|
|
".md",
|
|
".rst",
|
|
".log",
|
|
".sh",
|
|
".bat",
|
|
".ps1",
|
|
".psm1",
|
|
".psd1",
|
|
".ps1xml",
|
|
".pssc",
|
|
".reg",
|
|
".mof",
|
|
".mfl",
|
|
".xaml",
|
|
".xml",
|
|
".wsf",
|
|
".config",
|
|
".ini",
|
|
".inf",
|
|
".json5",
|
|
".hcl",
|
|
".tf",
|
|
".tfvars",
|
|
".tsv",
|
|
".properties",
|
|
]:
|
|
raise ValueError("Unsupported file type")
|
|
return ext.lower()
|
|
return v
|
|
|
|
def create(self, initial_content: str) -> None:
|
|
"""
|
|
Creates a new file artifact with the initial content.
|
|
"""
|
|
try:
|
|
self.contents = initial_content
|
|
self.versions.append(
|
|
FileVersion(
|
|
version_number=1,
|
|
content=initial_content,
|
|
timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
)
|
|
)
|
|
self.edit_count = 0
|
|
except Exception as e:
|
|
logger.error(f"Error creating artifact: {e}")
|
|
raise e
|
|
|
|
def edit(self, new_content: str) -> None:
|
|
"""
|
|
Edits the artifact's content, tracking the change in the version history.
|
|
"""
|
|
try:
|
|
self.contents = new_content
|
|
self.edit_count += 1
|
|
new_version = FileVersion(
|
|
version_number=len(self.versions) + 1,
|
|
content=new_content,
|
|
timestamp=time.strftime("%Y-%m-%d %H:%M:%S"),
|
|
)
|
|
self.versions.append(new_version)
|
|
except Exception as e:
|
|
logger.error(f"Error editing artifact: {e}")
|
|
raise e
|
|
|
|
def save(self) -> None:
|
|
"""
|
|
Saves the current artifact's contents to the specified file path.
|
|
"""
|
|
with open(self.file_path, "w") as f:
|
|
f.write(self.contents)
|
|
|
|
def load(self) -> None:
|
|
"""
|
|
Loads the file contents from the specified file path into the artifact.
|
|
"""
|
|
with open(self.file_path, "r") as f:
|
|
self.contents = f.read()
|
|
self.create(self.contents)
|
|
|
|
def get_version(
|
|
self, version_number: int
|
|
) -> Union[FileVersion, None]:
|
|
"""
|
|
Retrieves a specific version of the artifact by its version number.
|
|
"""
|
|
for version in self.versions:
|
|
if version.version_number == version_number:
|
|
return version
|
|
return None
|
|
|
|
def get_contents(self) -> str:
|
|
"""
|
|
Returns the current contents of the artifact as a string.
|
|
"""
|
|
return self.contents
|
|
|
|
def get_version_history(self) -> str:
|
|
"""
|
|
Returns the version history of the artifact as a formatted string.
|
|
"""
|
|
return "\n\n".join(
|
|
[str(version) for version in self.versions]
|
|
)
|
|
|
|
def export_to_json(self, file_path: str) -> None:
|
|
"""
|
|
Exports the artifact to a JSON file.
|
|
|
|
Args:
|
|
file_path (str): The path to the JSON file where the artifact will be saved.
|
|
"""
|
|
with open(file_path, "w") as json_file:
|
|
json.dump(self.dict(), json_file, default=str, indent=4)
|
|
|
|
@classmethod
|
|
def import_from_json(cls, file_path: str) -> "Artifact":
|
|
"""
|
|
Imports an artifact from a JSON file.
|
|
|
|
Args:
|
|
file_path (str): The path to the JSON file to import the artifact from.
|
|
|
|
Returns:
|
|
Artifact: The imported artifact instance.
|
|
"""
|
|
with open(file_path, "r") as json_file:
|
|
data = json.load(json_file)
|
|
# Convert timestamp strings back to datetime objects
|
|
for version in data["versions"]:
|
|
version["timestamp"] = datetime.fromisoformat(
|
|
version["timestamp"]
|
|
)
|
|
return cls(**data)
|
|
|
|
def get_metrics(self) -> str:
|
|
"""
|
|
Returns all metrics of the artifact as a formatted string.
|
|
|
|
Returns:
|
|
str: A string containing all metrics of the artifact.
|
|
"""
|
|
metrics = (
|
|
f"File Path: {self.file_path}\n"
|
|
f"File Type: {self.file_type}\n"
|
|
f"Current Contents:\n{self.contents}\n\n"
|
|
f"Edit Count: {self.edit_count}\n"
|
|
f"Version History:\n{self.get_version_history()}"
|
|
)
|
|
return metrics
|
|
|
|
def to_dict(self) -> Dict[str, Any]:
|
|
"""
|
|
Converts the artifact instance to a dictionary representation.
|
|
"""
|
|
return self.dict()
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: Dict[str, Any]) -> "Artifact":
|
|
"""
|
|
Creates an artifact instance from a dictionary representation.
|
|
"""
|
|
try:
|
|
# Convert timestamp strings back to datetime objects if necessary
|
|
for version in data.get("versions", []):
|
|
if isinstance(version["timestamp"], str):
|
|
version["timestamp"] = datetime.fromisoformat(
|
|
version["timestamp"]
|
|
)
|
|
return cls(**data)
|
|
except Exception as e:
|
|
logger.error(f"Error creating artifact from dict: {e}")
|
|
raise e
|
|
|
|
def save_as(self, output_format: str) -> None:
|
|
"""
|
|
Saves the artifact's contents in the specified format.
|
|
|
|
Args:
|
|
output_format (str): The desired output format ('.md', '.txt', '.pdf', '.py')
|
|
|
|
Raises:
|
|
ValueError: If the output format is not supported
|
|
"""
|
|
supported_formats = {".md", ".txt", ".pdf", ".py"}
|
|
|
|
if output_format not in supported_formats:
|
|
raise ValueError(
|
|
f"Unsupported output format. Supported formats are: {supported_formats}"
|
|
)
|
|
|
|
output_path = (
|
|
os.path.splitext(self.file_path)[0] + output_format
|
|
)
|
|
|
|
if output_format == ".pdf":
|
|
self._save_as_pdf(output_path)
|
|
else:
|
|
if output_format == ".md":
|
|
# Create the file in the specified folder
|
|
create_file_in_folder(
|
|
self.folder_path,
|
|
self.file_path,
|
|
f"{os.path.basename(self.file_path)}\n\n{self.contents}",
|
|
)
|
|
|
|
elif output_format == ".py":
|
|
# Add Python file header
|
|
create_file_in_folder(
|
|
self.folder_path,
|
|
self.file_path,
|
|
f"#{os.path.basename(self.file_path)}\n\n{self.contents}",
|
|
)
|
|
else: # .txt
|
|
create_file_in_folder(
|
|
self.folder_path,
|
|
self.file_path,
|
|
self.contents,
|
|
)
|
|
|
|
def _save_as_pdf(self, output_path: str) -> None:
|
|
"""
|
|
Helper method to save content as PDF using reportlab
|
|
"""
|
|
try:
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.pdfgen import canvas
|
|
except ImportError as e:
|
|
logger.error(f"Error importing reportlab: {e}")
|
|
subprocess.run(["pip", "install", "reportlab"])
|
|
from reportlab.lib.pagesizes import letter
|
|
from reportlab.pdfgen import canvas
|
|
|
|
c = canvas.Canvas(output_path, pagesize=letter)
|
|
# Split content into lines
|
|
y = 750 # Starting y position
|
|
for line in self.contents.split("\n"):
|
|
c.drawString(50, y, line)
|
|
y -= 15 # Move down for next line
|
|
if y < 50: # New page if bottom reached
|
|
c.showPage()
|
|
y = 750
|
|
c.save()
|
|
|
|
|
|
# # Example usage
|
|
# artifact = Artifact(file_path="example.txt", file_type=".txt")
|
|
# artifact.create("Initial content")
|
|
# artifact.edit("First edit")
|
|
# artifact.edit("Second edit")
|
|
# artifact.save()
|
|
|
|
# # Export to JSON
|
|
# artifact.export_to_json("artifact.json")
|
|
|
|
# # Import from JSON
|
|
# imported_artifact = Artifact.import_from_json("artifact.json")
|
|
|
|
# # # Get metrics
|
|
# print(artifact.get_metrics())
|
|
|
|
|
|
# Testing saving in different artifact types
|
|
# Create an artifact
|
|
# artifact = Artifact(file_path="/path/to/file", file_type=".txt",contents="", edit_count=0 )
|
|
# artifact.create("This is some content\nWith multiple lines")
|
|
|
|
# Save in different formats
|
|
# artifact.save_as(".md") # Creates example.md
|
|
# artifact.save_as(".txt") # Creates example.txt
|
|
# artifact.save_as(".pdf") # Creates example.pdf
|
|
# artifact.save_as(".py") # Creates example.py
|