Spaces:
Running
Running
| """ | |
| PURE LLM-DRIVEN VISUALIZATION - ZERO HARDCODING | |
| ================================================= | |
| This is the ULTIMATE autonomous chart system. | |
| The LLM generates the COMPLETE Plotly configuration. | |
| NO predefined chart types. | |
| NO hardcoded generators. | |
| LLM decides EVERYTHING based on: | |
| 1. User's query | |
| 2. User's actual data | |
| 3. What makes sense for the visualization | |
| Just like ChatGPT - pure intelligence, pure autonomy. | |
| """ | |
| import json | |
| import logging | |
| from typing import Optional | |
| import pandas as pd | |
| from core.llm import chat | |
| logger = logging.getLogger(__name__) | |
| def generate_visualization_pure_llm(query: str, df: pd.DataFrame) -> Optional[str]: | |
| """ | |
| 100% LLM-DRIVEN VISUALIZATION. | |
| The LLM decides: | |
| - IF a chart should be generated | |
| - WHAT type of chart | |
| - WHICH columns to use | |
| - HOW to structure the data | |
| - ALL styling and configuration | |
| Returns complete Plotly JSON - no hardcoding! | |
| """ | |
| if df is None or df.empty: | |
| return None | |
| try: | |
| # Prepare data context for LLM | |
| columns = list(df.columns) | |
| dtypes = {col: str(df[col].dtype) for col in columns} | |
| sample_data = df.head(10).to_dict(orient='records') | |
| row_count = len(df) | |
| # For numeric columns, get basic stats | |
| numeric_summary = {} | |
| for col in df.select_dtypes(include=['int64', 'float64']).columns: | |
| numeric_summary[col] = { | |
| "min": float(df[col].min()), | |
| "max": float(df[col].max()), | |
| "sum": float(df[col].sum()), | |
| "mean": float(df[col].mean()) | |
| } | |
| # For categorical columns, get value counts | |
| categorical_summary = {} | |
| for col in df.select_dtypes(include=['object']).columns[:5]: | |
| categorical_summary[col] = df[col].value_counts().head(10).to_dict() | |
| # THE MASTER PROMPT - LLM does EVERYTHING | |
| prompt = f"""You are an expert data visualization AI. Generate a Plotly chart configuration. | |
| USER REQUEST: "{query}" | |
| DATA AVAILABLE: | |
| - Columns: {columns} | |
| - Data Types: {dtypes} | |
| - Total Rows: {row_count} | |
| - Sample Data: {json.dumps(sample_data[:5], default=str)[:1000]} | |
| NUMERIC DATA SUMMARY: | |
| {json.dumps(numeric_summary, indent=2)[:800]} | |
| CATEGORICAL DATA SUMMARY: | |
| {json.dumps(categorical_summary, default=str, indent=2)[:800]} | |
| YOUR TASK: | |
| 1. Analyze what the user is asking for | |
| 2. Decide the BEST visualization (or no chart if not needed) | |
| 3. Generate a COMPLETE Plotly chart configuration using the ACTUAL data values above | |
| IMPORTANT RULES: | |
| - Use ONLY the actual data values shown above | |
| - Choose the visualization that best answers the user's question | |
| - Include proper titles, labels, and styling | |
| - Use dark theme colors (paper_bgcolor: "rgba(0,0,0,0)", font color: "#9ca3af") | |
| - Accent colors: teal (#14b8a6), indigo (#6366f1), amber (#f59e0b) | |
| - For pie/donut charts, use "labels" and "values" (NEVER "x" and "y") in the data array. | |
| Return your response in this EXACT format: | |
| {{ | |
| "should_generate": true or false, | |
| "visualization_type": "what you chose and why", | |
| "plotly_chart": {{COMPLETE PLOTLY JSON HERE}} | |
| }} | |
| If no chart is needed, return: | |
| {{"should_generate": false, "reason": "why no chart needed"}} | |
| Generate the visualization:""" | |
| response = chat(prompt, temperature=0.2, max_tokens=2000) | |
| # Parse the response | |
| response = response.strip() | |
| # Find JSON in response | |
| if '```json' in response: | |
| response = response.split('```json')[1].split('```')[0] | |
| elif '```' in response: | |
| response = response.split('```')[1].split('```')[0] | |
| # Find the JSON object | |
| start = response.find('{') | |
| end = response.rfind('}') + 1 | |
| if start >= 0 and end > start: | |
| response = response[start:end] | |
| result = json.loads(response) | |
| # Check if we should generate | |
| if not result.get("should_generate", True): | |
| logger.info(f"LLM decided not to generate chart: {result.get('reason', 'No reason')}") | |
| return None | |
| # Get the Plotly chart configuration | |
| plotly_config = result.get("plotly_chart", {}) | |
| if not plotly_config or "data" not in plotly_config: | |
| logger.warning("LLM returned invalid Plotly config") | |
| return None | |
| # Ensure dark theme styling | |
| if "layout" not in plotly_config: | |
| plotly_config["layout"] = {} | |
| plotly_config["layout"]["paper_bgcolor"] = "rgba(0,0,0,0)" | |
| plotly_config["layout"]["plot_bgcolor"] = "rgba(0,0,0,0)" | |
| plotly_config["layout"]["font"] = {"color": "#9ca3af"} | |
| if "title" in plotly_config["layout"]: | |
| if isinstance(plotly_config["layout"]["title"], str): | |
| plotly_config["layout"]["title"] = { | |
| "text": plotly_config["layout"]["title"], | |
| "font": {"color": "#fff"} | |
| } | |
| elif isinstance(plotly_config["layout"]["title"], dict): | |
| plotly_config["layout"]["title"]["font"] = {"color": "#fff"} | |
| logger.info(f"LLM generated {result.get('visualization_type', 'chart')}") | |
| return f"\n\n```plotly_chart\n{json.dumps(plotly_config)}\n```" | |
| except json.JSONDecodeError as e: | |
| logger.error(f"Failed to parse LLM response as JSON: {e}") | |
| return None | |
| except Exception as e: | |
| logger.error(f"Pure LLM visualization error: {e}") | |
| return None | |
| def should_visualize(query: str) -> bool: | |
| """ | |
| Quick check if query likely needs visualization. | |
| This is just a hint - LLM makes final decision. | |
| """ | |
| viz_hints = [ | |
| 'chart', 'graph', 'plot', 'visualize', 'show', 'display', | |
| 'pie', 'bar', 'line', 'scatter', 'distribution', | |
| 'top', 'bottom', 'compare', 'trend', 'breakdown', | |
| 'heatmap', 'histogram', 'funnel', 'gauge', 'mindmap', | |
| 'how many', 'what is the total', 'percentage', | |
| 'img', 'image', 'picture', 'dashboard', 'report' | |
| ] | |
| query_lower = query.lower() | |
| return any(hint in query_lower for hint in viz_hints) | |
| def generate_smart_chart(query: str, df: pd.DataFrame) -> Optional[str]: | |
| """ | |
| Main entry point for smart chart generation. | |
| This is 100% autonomous: | |
| 1. Checks if visualization might be needed | |
| 2. Calls LLM to generate complete chart | |
| 3. Returns Plotly JSON or None | |
| """ | |
| # Quick check for visualization hints | |
| if not should_visualize(query): | |
| # Still let LLM decide - it might choose to visualize anyway | |
| pass | |
| return generate_visualization_pure_llm(query, df) | |
| # Backwards compatibility aliases | |
| def smart_chart(query: str, df: pd.DataFrame) -> Optional[str]: | |
| """Alias for generate_smart_chart for backwards compatibility""" | |
| return generate_smart_chart(query, df) | |
| def get_color_palette_from_query(query: str) -> list: | |
| """Get color palette - returns default teal/indigo theme""" | |
| return ["#14b8a6", "#6366f1", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#84cc16", "#f97316"] | |