import json import os from datetime import datetime from pathlib import Path import html from collections import defaultdict # Import defaultdict import webbrowser # Define file paths (assuming they are in the same directory as the script) BASE_DIR = Path(__file__).resolve().parent SESSION_DATA_FILE = BASE_DIR / "session.jsonl" COLOR_CLASSES = ["teal", "green", "yellow", "red"] # For dynamic history item colors DASHBOARD_TEMPLATE_FILE = BASE_DIR / "dashboard.html" DASHBOARD_OUTPUT_FILE = BASE_DIR / "dashboard_generated.html" def format_timestamp(ts_str): """Formats an ISO timestamp string into a more readable format.""" if not ts_str: return "N/A" try: # Handle potential 'Z' for UTC if ts_str.endswith('Z'): ts_str = ts_str[:-1] + '+00:00' dt_obj = datetime.fromisoformat(ts_str) return dt_obj.strftime("%Y-%m-%d %H:%M:%S") except ValueError: return ts_str # Return original if parsing fails def format_duration(seconds): """Formats a duration in seconds into a human-readable string (e.g., 1m 38s).""" if seconds is None: return "N/A" try: s = int(seconds) if s < 0: return "N/A" m, s = divmod(s, 60) h, m = divmod(m, 60) if h > 0: return f"{h}h {m}m {s}s" elif m > 0: return f"{m}m {s}s" else: return f"{s}s" except (ValueError, TypeError): return "N/A" def escape_html(text): """Escapes HTML special characters in a string.""" if text is None: return "" return html.escape(str(text)) def read_session_data(filepath): """Reads session data from a JSONL file.""" data = [] if not filepath.exists(): print(f"Error: Session data file not found at {filepath}") return data with open(filepath, "r", encoding="utf-8") as f: for line in f: try: data.append(json.loads(line)) except json.JSONDecodeError as e: print(f"Error decoding JSON from line: {line.strip()} - {e}") return data def calculate_cost_by_model(all_data): """ Calculates the total estimated cost per model from all session data. """ cost_by_model = defaultdict(float) if not all_data: return dict(cost_by_model) for data in all_data: # Iterate through the models used summary for this interaction models_summary = data.get("models_used_summary", []) if not isinstance(models_summary, list): # print(f"Warning: 'models_used_summary' is not a list in data: {data}") # Optional debug continue for model_info in models_summary: if not isinstance(model_info, dict): # print(f"Warning: Item in 'models_used_summary' is not a dict in data: {data}") # Optional debug continue model_name = model_info.get("name", "Unknown Model") cost = model_info.get("cost", 0.0) # Ensure cost is a number before adding if isinstance(cost, (int, float)): cost_by_model[model_name] += cost else: print(f"Warning: Found non-numeric cost value for model '{model_name}': {cost} in data: {data}") return dict(cost_by_model) # Convert defaultdict to dict for final return def format_cost_by_model_html(cost_by_model): """Generates HTML list for cost breakdown by model.""" if not cost_by_model: return "
{escape_html(title)}: None
" list_items_html = "".join(f"No token summary available.
" return f"""Prompt Tokens: {token_summary.get("prompt_tokens", "N/A")}
Completion Tokens: {token_summary.get("completion_tokens", "N/A")}
Total Tokens: {token_summary.get("total_tokens", "N/A")}
Estimated Cost: ${token_summary.get("estimated_cost", 0.0):.6f}
No models used summary available.
" rows_html = "" for model_info in models_summary: model_info = model_info or {} # Ensure model_info is not None rows_html += f"""Name | Calls | Cost | Prompt Tokens | Completion Tokens |
---|
No LLM call details available.
" rows_html = "" for call in llm_calls: call = call or {} # Ensure call is not None rows_html += f"""Model | ID | Finish Reason | Prompt Tokens | Completion Tokens | Cost | Timestamp |
---|
Query: {query_text}
No latest interaction data to display.
' history_entries_html = 'No interaction history to display.
' else: # Data is assumed to be oldest to newest from read_session_data data_for_processing = list(all_session_data) # Make a copy latest_interaction_data = data_for_processing.pop() # Removes and returns the last item (newest) project_name_header = escape_html(latest_interaction_data.get("project_name", "AIDER ANALYTICS")) # Get project name from latest interaction # Index 0 for latest, but color is overridden by use_special_color_bar latest_interaction_display_html = generate_interaction_html(latest_interaction_data, 0, use_special_color_bar=True, special_color_class="blue") history_entries_html_parts = [] if not data_for_processing: history_entries_html = 'No further interaction history to display.
' else: # Iterate from newest to oldest for display for the rest of the history for i, interaction_data in enumerate(reversed(data_for_processing)): # i will be 0 for the newest in remaining, 1 for next, etc. history_entries_html_parts.append(generate_interaction_html(interaction_data, i)) history_entries_html = "\n".join(history_entries_html_parts) if not history_entries_html_parts: # Should not happen if data_for_processing was not empty history_entries_html = 'No further interaction history to display.
' if not DASHBOARD_TEMPLATE_FILE.exists(): print(f"Error: Dashboard template file not found at {DASHBOARD_TEMPLATE_FILE}") # Create a basic HTML structure if template is missing, to show some output output_content = f"""Note: dashboard.html template was not found. This is a fallback display.
""" else: with open(DASHBOARD_TEMPLATE_FILE, "r", encoding="utf-8") as f: template_content = f.read() output_content = template_content.replace("", project_name_header) output_content = output_content.replace("", stats_overview_html) output_content = output_content.replace("", secondary_stats_html) output_content = output_content.replace("", latest_interaction_display_html) output_content = output_content.replace("", history_entries_html) # Check if placeholders were correctly replaced (optional, for debugging) # if "" in output_content and "" not in stats_overview_html: # print("Warning: Stats overview placeholder was not replaced.") # if "" in output_content and "" not in secondary_stats_html: # print("Warning: Secondary stats placeholder was not replaced.") # if "" in output_content and "" not in latest_interaction_display_html: # print("Warning: Latest interaction placeholder was not replaced.") # if "" in output_content and "" not in history_entries_html: # print("Warning: History entries placeholder was not replaced.") with open(DASHBOARD_OUTPUT_FILE, "w", encoding="utf-8") as f: f.write(output_content) print(f"Dashboard generated: {DASHBOARD_OUTPUT_FILE.resolve().as_uri()}") webbrowser.open(DASHBOARD_OUTPUT_FILE.resolve().as_uri()) if __name__ == "__main__": main()