mirrored 17 minutes ago
0
HiroidAdd multiple new modules and tools to enhance the functionality and extensibility of the Maestro project (#333) * Added a **pyproject.toml** file to define project metadata and dependencies. * Added **run\_maestro.py** and **osworld\_run\_maestro.py** to provide the main execution logic. * Introduced multiple new modules, including **Evaluator**, **Controller**, **Manager**, and **Sub-Worker**, supporting task planning, state management, and data analysis. * Added a **tools module** containing utility functions and tool configurations to improve code reusability. * Updated the **README** and documentation with usage examples and module descriptions. These changes lay the foundation for expanding the Maestro project’s functionality and improving the user experience. Co-authored-by: Hiroid <guoliangxuan@deepmatrix.com>3a4b673
#!/usr/bin/env python
"""
Display Viewer - Used to display operation records in display.json file in chronological order

Usage:
    python -m lybicguiagents.gui_agents.utils.display_viewer --file /path/to/display.json [--output text|json] [--filter module1,module2]
"""

import os
import sys
import json
import argparse
import datetime
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple


def load_display_json(file_path: str) -> Dict:
    """
    Load display.json file
    
    Args:
        file_path: Path to display.json file
        
    Returns:
        Parsed JSON data
    """
    try:
        try:
            with open(file_path, 'r', encoding='utf-8') as f:
                return json.load(f)
        except UnicodeDecodeError:
            print(
                f"Warning: Failed to decode '{file_path}' with utf-8, retrying with GB2312..."
            )
            with open(file_path, 'r', encoding='gb2312') as f:
                return json.load(f)
    except FileNotFoundError:
        print(f"Error: File '{file_path}' does not exist")
        sys.exit(1)
    except json.JSONDecodeError:
        print(f"Error: File '{file_path}' is not a valid JSON format")
        sys.exit(1)
    except Exception as e:
        print(f"Error: An error occurred while reading file '{file_path}': {e}")
        sys.exit(1)


def flatten_operations(data: Dict) -> List[Dict]:
    """
    Flatten all module operation records into a time-sorted list
    
    Args:
        data: display.json data
        
    Returns:
        List of operation records sorted by time
    """
    all_operations = []

    if "operations" not in data:
        return all_operations

    for module, operations in data["operations"].items():
        for op in operations:
            # Add module information
            op["module"] = module
            all_operations.append(op)

    # Sort by timestamp
    all_operations.sort(key=lambda x: x.get("timestamp", 0))

    return all_operations


def format_timestamp(timestamp: float) -> str:
    """
    Format timestamp into readable datetime
    
    Args:
        timestamp: UNIX timestamp
        
    Returns:
        Formatted datetime string
    """
    dt = datetime.datetime.fromtimestamp(timestamp)
    return dt.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]


def format_duration(duration: float) -> str:
    """
    Format duration
    
    Args:
        duration: Duration (seconds)
        
    Returns:
        Formatted duration string
    """
    if duration < 0.001:
        return f"{duration * 1000000:.2f}μs"
    elif duration < 1:
        return f"{duration * 1000:.2f}ms"
    else:
        return f"{duration:.2f}s"


def format_tokens(tokens: List[int]) -> str:
    """
    Format tokens information
    
    Args:
        tokens: [input tokens, output tokens, total tokens]
        
    Returns:
        Formatted tokens string
    """
    if not tokens or len(tokens) < 3:
        return "N/A"

    return f"in:{tokens[0]} out:{tokens[1]} total:{tokens[2]}"


def truncate_text(text: str, max_length: int = 100) -> str:
    """
    Truncate text, add ellipsis when exceeding maximum length
    
    Args:
        text: Original text
        max_length: Maximum length
        
    Returns:
        Truncated text
    """
    if not text:
        return ""

    if isinstance(text, (dict, list)):
        text = str(text)

    if len(text) <= max_length:
        return text

    return text[:max_length - 3] + "..."


def find_latest_display_json() -> Optional[str]:
    """
    Find the latest display.json file
    
    Returns:
        Path to the latest display.json file, or None if not found
    """
    # Look for the runtime folder in the current directory
    runtime_dir = Path("runtime")
    if not runtime_dir.exists() or not runtime_dir.is_dir():
        # Try looking in the parent directory
        parent_runtime = Path("..") / "runtime"
        if parent_runtime.exists() and parent_runtime.is_dir():
            runtime_dir = parent_runtime
        else:
            return None

    # Find all timestamp folders
    timestamp_dirs = [d for d in runtime_dir.iterdir() if d.is_dir()]
    if not timestamp_dirs:
        return None

    # Sort by folder name (timestamp) and take the latest
    latest_dir = sorted(timestamp_dirs)[-1]
    display_file = latest_dir / "display.json"

    if display_file.exists():
        return str(display_file)

    return None


def main():
    parser = argparse.ArgumentParser(
        description=
        "Display operation records in display.json file in chronological order")
    parser.add_argument("--file", help="Path to display.json file")
    parser.add_argument("--dir", help="Path to directory containing display.json files (recursive)")
    parser.add_argument("--output",
                        choices=["text", "json"],
                        default="text",
                        help="Output format (default: text)")
    parser.add_argument(
        "--filter",
        help="Modules to filter, separated by commas (e.g., manager,worker)")

    args = parser.parse_args()

    if args.file and args.dir:
        print("Error: --file and --dir cannot be used together")
        sys.exit(1)

    def process_one_file(file_path: str):
        # Load data
        data = load_display_json(file_path)
        # Flatten and sort operations
        operations = flatten_operations(data)
        # Handle module filtering
        filter_modules = None
        if args.filter:
            filter_modules = [module.strip() for module in args.filter.split(",")]
        # Generate output content
        output_content = ""
        if args.output == "json":
            # Filter operations if modules are specified
            if filter_modules:
                filtered_ops = [op for op in operations if op["module"] in filter_modules]
            else:
                filtered_ops = operations
            output_content = json.dumps(filtered_ops, indent=2, ensure_ascii=False)
        else:
            # Generate text format output
            output_lines = []
            for i, op in enumerate(operations):
                # Skip modules that don't match the filter if a filter is specified
                if filter_modules and op["module"] not in filter_modules:
                    continue
                module = op["module"]
                operation = op.get("operation", "unknown")
                timestamp = format_timestamp(op.get("timestamp", 0))
                # Output basic information
                output_lines.append(f"{i+1:3d} | {timestamp} | {module:10} | {operation}")
                # Output detailed information
                if "duration" in op:
                    output_lines.append(f"     └─ Duration: {format_duration(op['duration'])}")
                if "tokens" in op:
                    output_lines.append(f"     └─ Tokens: {format_tokens(op['tokens'])}")
                if "cost" in op:
                    output_lines.append(f"     └─ Cost: {op['cost']}")
                if "content" in op:
                    content = op["content"]
                    output_lines.append(f"     └─ Content: {content}")
                if "status" in op:
                    output_lines.append(f"     └─ Status: {op['status']}")
                output_lines.append("")
            output_content = "\n".join(output_lines)
        # Write output to file
        input_path = Path(file_path)
        output_filename = f"display_viewer_output_{args.output}.txt"
        output_path = input_path.parent / output_filename
        try:
            with open(output_path, 'w', encoding='utf-8') as f:
                f.write(output_content)
            print(f"Output written to: {output_path}")
        except Exception as e:
            print(f"Error writing output file: {e}")
            sys.exit(1)

    if args.dir:
        for root, dirs, files in os.walk(args.dir):
            for file in files:
                if file == "display.json":
                    file_path = os.path.join(root, file)
                    print(f"Processing: {file_path}")
                    process_one_file(file_path)
        return

    file_path = args.file
    if not file_path:
        file_path = find_latest_display_json()
        if not file_path:
            print(
                "Error: Cannot find display.json file, please specify file path using --file parameter"
            )
            sys.exit(1)
        print(f"Using the latest display.json file: {file_path}")
    process_one_file(file_path)


if __name__ == "__main__":
    """
    python display_viewer.py --file 
    python display_viewer.py --dir 
    """
    main()