#!/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()