#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ 生成JSON跟踪文件的可视化HTML页面 """ import json import os from pathlib import Path def load_json_data(json_path): """加载JSON数据""" with open(json_path, 'r', encoding='utf-8') as f: return json.load(f) def calculate_grid_layout(nodes, node_width=200, node_height=120, horizontal_spacing=250, vertical_spacing=150, margin=50, screen_width=1600): """ 计算网格布局的节点位置 从左到右,到右边后向下再向左,呈蛇形排列,所有节点对齐到网格 """ # 按sequence排序 sorted_nodes = sorted(nodes, key=lambda x: x.get('sequence', 0)) positions = {} grid_positions = {} # 存储每个节点在网格中的行列位置 col = 0 row = 0 direction = 1 # 1表示向右,-1表示向左 # 计算每行可以放置的节点数 available_width = screen_width - 2 * margin nodes_per_row = max(1, int(available_width / (node_width + horizontal_spacing))) for node in sorted_nodes: seq = node.get('sequence', 0) # 检查是否需要换行 if direction == 1 and col >= nodes_per_row: # 向右超出,向下移动,开始向左 row += 1 col = nodes_per_row - 1 direction = -1 elif direction == -1 and col < 0: # 向左超出,向下移动,开始向右 row += 1 col = 0 direction = 1 # 记录网格位置 grid_positions[seq] = {'row': row, 'col': col} # 计算实际像素位置(对齐到网格) x = margin + col * (node_width + horizontal_spacing) y = margin + row * (node_height + vertical_spacing) positions[seq] = { 'x': x, 'y': y, 'width': node_width, 'height': node_height, 'row': row, 'col': col } # 移动到下一个网格位置 col += direction # 计算最大尺寸 max_col = max([pos['col'] for pos in grid_positions.values()]) if grid_positions else 0 max_row = max([pos['row'] for pos in grid_positions.values()]) if grid_positions else 0 max_x = margin + (max_col + 1) * (node_width + horizontal_spacing) max_y = margin + (max_row + 1) * (node_height + vertical_spacing) return positions, grid_positions, max_x, max_y def generate_html(json_data, output_path): """生成HTML可视化页面""" # 提取所有节点 nodes = [] node_map = {} for item in json_data: seq = item.get('sequence') if seq is not None: nodes.append(item) node_map[seq] = item # 计算网格布局 positions, grid_positions, max_width, max_height = calculate_grid_layout(nodes) # 计算连线信息 def calculate_connection(from_seq, to_seq, from_pos, to_pos): """计算两个节点之间的连线方向和起止点""" from_row, from_col = from_pos.get('row', 0), from_pos.get('col', 0) to_row, to_col = to_pos.get('row', 0), to_pos.get('col', 0) # 判断方向 if to_col > from_col: direction = 'right' # 下一个节点在右侧 elif to_row > from_row: direction = 'down' # 下一个节点在下侧 elif to_col < from_col: direction = 'left' # 下一个节点在左侧 else: direction = 'down' # 默认向下 # 计算起止点(节点的最近边) from_x = from_pos['x'] from_y = from_pos['y'] from_w = from_pos['width'] from_h = from_pos['height'] to_x = to_pos['x'] to_y = to_pos['y'] to_w = to_pos['width'] to_h = to_pos['height'] if direction == 'right': # 从右侧边中点连接到左侧边中点 start_x = from_x + from_w start_y = from_y + from_h / 2 end_x = to_x end_y = to_y + to_h / 2 elif direction == 'down': # 从下侧边中点连接到上侧边中点 start_x = from_x + from_w / 2 start_y = from_y + from_h end_x = to_x + to_w / 2 end_y = to_y elif direction == 'left': # 从左侧边中点连接到右侧边中点 start_x = from_x start_y = from_y + from_h / 2 end_x = to_x + to_w end_y = to_y + to_h / 2 return { 'direction': direction, 'start_x': start_x, 'start_y': start_y, 'end_x': end_x, 'end_y': end_y } # 生成连线数据(按照sequence顺序连接相邻节点) connections = [] sorted_sequences = sorted([node.get('sequence') for node in nodes if node.get('sequence') is not None]) for i in range(len(sorted_sequences) - 1): from_seq = sorted_sequences[i] to_seq = sorted_sequences[i + 1] if from_seq in positions and to_seq in positions: conn = calculate_connection(from_seq, to_seq, positions[from_seq], positions[to_seq]) conn['from'] = from_seq conn['to'] = to_seq connections.append(conn) # 准备传递给JavaScript的数据(简化节点数据,避免循环引用) nodes_js = [] for node in nodes: node_js = { 'sequence': node.get('sequence'), 'role': node.get('role', 'unknown'), 'parent_sequence': node.get('parent_sequence'), 'status': node.get('status', 'unknown'), 'title': node.get('title', '无标题'), 'text': node.get('text', ''), 'tokens': node.get('tokens', 0) } # 处理content字段 content = node.get('content') if content: if isinstance(content, str): node_js['content'] = content else: node_js['content'] = json.dumps(content, ensure_ascii=False, indent=2) # 处理children字段 children = node.get('children') if children: node_js['children'] = json.dumps(children, ensure_ascii=False, indent=2) nodes_js.append(node_js) # 生成HTML html_content = f""" 执行跟踪可视化
总节点数: {len(nodes)}
系统节点: 0
用户节点: 0
助手节点: 0
""" # 写入文件 with open(output_path, 'w', encoding='utf-8') as f: f.write(html_content) print(f"✅ 可视化页面已生成: {output_path}") def main(): """主函数""" # 获取脚本所在目录 script_dir = Path(__file__).parent # JSON文件路径 json_path = script_dir / '.trace' / '6bddb982-21db-4cbc-b064-8a568ce0791d' / 'output.json' # 输出HTML文件路径 output_path = script_dir / 'trace_visualization.html' if not json_path.exists(): print(f"❌ 错误: 找不到JSON文件: {json_path}") return print(f"📖 正在读取JSON文件: {json_path}") json_data = load_json_data(json_path) print(f"📊 找到 {len(json_data)} 个节点") print(f"🎨 正在生成可视化页面...") generate_html(json_data, output_path) print(f"\n✨ 完成! 请在浏览器中打开: {output_path}") if __name__ == '__main__': main()