Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| from openai import OpenAI | |
| import os | |
| import base64 | |
| from PIL import Image | |
| import io | |
| client = OpenAI( | |
| base_url="https://openrouter.ai/api/v1", | |
| api_key=os.environ.get("OPENROUTER_API_KEY") | |
| ) | |
| def image_to_base64(image): | |
| pil_image = Image.fromarray(image) | |
| buffer = io.BytesIO() | |
| pil_image.save(buffer, format="PNG") | |
| encoded = base64.b64encode(buffer.getvalue()).decode("utf-8") | |
| return encoded | |
| def analyze_chart(image, analysis_type): | |
| if image is None: | |
| return "β οΈ Please upload a chart image first." | |
| prompts = { | |
| "Full Analyst Report": """ | |
| You are a senior data analyst. Analyze this chart and provide: | |
| π CHART TYPE & OVERVIEW | |
| - What type of chart is this? | |
| - What is the main topic/dataset? | |
| π KEY TRENDS & PATTERNS | |
| - What are the major trends visible? | |
| - Are there any peaks, dips, or anomalies? | |
| π DEEP INSIGHTS | |
| - What are the 3 most important insights? | |
| - What story does this data tell? | |
| β RECOMMENDATIONS | |
| - What actions would you recommend based on this data? | |
| - What should be monitored going forward? | |
| Write in a professional analyst tone. | |
| """, | |
| "Quick Summary": """ | |
| Look at this chart and give a SHORT 5-line summary: | |
| - What the chart shows | |
| - The biggest trend | |
| - The most important number or data point | |
| - One anomaly if any | |
| - One recommendation | |
| """, | |
| "Find Anomalies": """ | |
| You are a data quality expert. Examine this chart carefully and: | |
| - Identify ALL anomalies, outliers, or unusual patterns | |
| - Explain why each one is unusual | |
| - Rate the severity (Low / Medium / High) | |
| - Suggest possible reasons for each anomaly | |
| """, | |
| "Compare & Contrast": """ | |
| Analyze this chart and: | |
| - Identify all data series or categories being compared | |
| - Compare them against each other in detail | |
| - Identify which is highest, lowest, growing fastest, declining | |
| - Explain what the comparison reveals about the subject | |
| """ | |
| } | |
| prompt = prompts[analysis_type] | |
| try: | |
| img_base64 = image_to_base64(image) | |
| response = client.chat.completions.create( | |
| model="nex-agi/nex-n2-pro:free", | |
| messages=[ | |
| { | |
| "role": "user", | |
| "content": [ | |
| { | |
| "type": "image_url", | |
| "image_url": { | |
| "url": f"data:image/png;base64,{img_base64}" | |
| } | |
| }, | |
| { | |
| "type": "text", | |
| "text": prompt | |
| } | |
| ] | |
| } | |
| ] | |
| ) | |
| return response.choices[0].message.content | |
| except Exception as e: | |
| return f"β Error: {str(e)}" | |
| custom_css = """ | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); | |
| * { font-family: 'Inter', sans-serif; } | |
| body { background: linear-gradient(135deg, #f5f0ff 0%, #e8f4fd 100%); } | |
| .gradio-container { | |
| max-width: 1100px !important; | |
| margin: auto !important; | |
| } | |
| /* Hero Header */ | |
| .hero-header { | |
| background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); | |
| border-radius: 20px; | |
| padding: 40px; | |
| text-align: center; | |
| margin-bottom: 24px; | |
| box-shadow: 0 8px 32px rgba(106,17,203,0.3); | |
| } | |
| .hero-header h1 { | |
| color: white !important; | |
| font-size: 2.8em !important; | |
| font-weight: 700 !important; | |
| margin: 0 !important; | |
| letter-spacing: -0.5px; | |
| } | |
| .hero-header p { | |
| color: rgba(255,255,255,0.85) !important; | |
| font-size: 1.1em !important; | |
| margin-top: 8px !important; | |
| } | |
| .badge { | |
| display: inline-block; | |
| background: rgba(255,255,255,0.2); | |
| color: white; | |
| padding: 4px 14px; | |
| border-radius: 20px; | |
| font-size: 0.85em; | |
| margin-top: 12px; | |
| border: 1px solid rgba(255,255,255,0.3); | |
| } | |
| /* Stats Row */ | |
| .stats-row { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 20px; | |
| } | |
| .stat-card { | |
| flex: 1; | |
| background: white; | |
| border-radius: 12px; | |
| padding: 16px; | |
| text-align: center; | |
| box-shadow: 0 2px 12px rgba(0,0,0,0.06); | |
| border-top: 3px solid #6a11cb; | |
| } | |
| .stat-card .number { | |
| font-size: 1.8em; | |
| font-weight: 700; | |
| color: #6a11cb; | |
| } | |
| .stat-card .label { | |
| font-size: 0.8em; | |
| color: #78909c; | |
| margin-top: 4px; | |
| } | |
| /* Upload Card */ | |
| .upload-card { | |
| background: white; | |
| border-radius: 16px; | |
| padding: 24px; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| border: 1px solid #ede7f6; | |
| } | |
| /* Result Card */ | |
| .result-card { | |
| background: white; | |
| border-radius: 16px; | |
| padding: 24px; | |
| box-shadow: 0 4px 20px rgba(0,0,0,0.08); | |
| border: 1px solid #e3f2fd; | |
| } | |
| /* Tips Box */ | |
| .tips-box { | |
| background: linear-gradient(135deg, #ede7f6, #e3f2fd); | |
| border-radius: 12px; | |
| padding: 16px 20px; | |
| margin-top: 16px; | |
| border-left: 4px solid #6a11cb; | |
| } | |
| .tips-box p { | |
| margin: 4px 0 !important; | |
| color: #37474f !important; | |
| font-size: 0.9em !important; | |
| } | |
| /* Analyze Button */ | |
| .analyze-btn { | |
| background: linear-gradient(135deg, #6a11cb, #2575fc) !important; | |
| border: none !important; | |
| border-radius: 12px !important; | |
| font-size: 1.1em !important; | |
| font-weight: 600 !important; | |
| padding: 14px !important; | |
| letter-spacing: 0.3px !important; | |
| box-shadow: 0 4px 15px rgba(106,17,203,0.4) !important; | |
| transition: all 0.3s ease !important; | |
| } | |
| .analyze-btn:hover { | |
| transform: translateY(-2px) !important; | |
| box-shadow: 0 6px 20px rgba(106,17,203,0.5) !important; | |
| } | |
| /* Dropdown */ | |
| select { | |
| border-radius: 10px !important; | |
| border: 1.5px solid #ede7f6 !important; | |
| padding: 10px !important; | |
| font-size: 0.95em !important; | |
| color: #37474f !important; | |
| } | |
| /* Textbox */ | |
| textarea { | |
| border-radius: 12px !important; | |
| border: 1.5px solid #e3f2fd !important; | |
| font-size: 0.95em !important; | |
| line-height: 1.7 !important; | |
| color: #263238 !important; | |
| background: #fafffe !important; | |
| } | |
| /* Image upload area */ | |
| .image-upload { | |
| border-radius: 12px !important; | |
| border: 2px dashed #ce93d8 !important; | |
| } | |
| /* Footer */ | |
| .footer-note { | |
| background: linear-gradient(135deg, #f3e5f5, #e8f4fd); | |
| border-radius: 12px; | |
| padding: 14px 20px; | |
| margin-top: 20px; | |
| text-align: center; | |
| border-left: 4px solid #2575fc; | |
| } | |
| .footer-note p { | |
| color: #37474f !important; | |
| font-size: 0.88em !important; | |
| margin: 0 !important; | |
| } | |
| """ | |
| with gr.Blocks(css=custom_css, title="π ChartTalker") as app: | |
| # Hero Header | |
| gr.HTML(""" | |
| <div class="hero-header"> | |
| <h1>π ChartTalker</h1> | |
| <p>Upload any chart β Get an instant analyst-level report in seconds</p> | |
| <span class="badge">π€ Powered by AI Vision Β· Free Β· Instant</span> | |
| </div> | |
| """) | |
| # Stats Row | |
| gr.HTML(""" | |
| <div class="stats-row"> | |
| <div class="stat-card"> | |
| <div class="number">π</div> | |
| <div class="label">Full Report</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="number">β‘</div> | |
| <div class="label">Quick Summary</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="number">π</div> | |
| <div class="label">Find Anomalies</div> | |
| </div> | |
| <div class="stat-card"> | |
| <div class="number">βοΈ</div> | |
| <div class="label">Compare & Contrast</div> | |
| </div> | |
| </div> | |
| """) | |
| with gr.Row(equal_height=True): | |
| # Left Column | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="upload-card">') | |
| gr.HTML('<h3 style="color:#6a11cb; margin:0 0 16px 0;">π€ Upload Your Chart</h3>') | |
| image_input = gr.Image( | |
| label="", | |
| type="numpy", | |
| height=280, | |
| elem_classes=["image-upload"] | |
| ) | |
| analysis_type = gr.Dropdown( | |
| choices=[ | |
| "Full Analyst Report", | |
| "Quick Summary", | |
| "Find Anomalies", | |
| "Compare & Contrast" | |
| ], | |
| value="Full Analyst Report", | |
| label="π Choose Analysis Type" | |
| ) | |
| analyze_btn = gr.Button( | |
| "π Analyze Chart", | |
| variant="primary", | |
| size="lg", | |
| elem_classes=["analyze-btn"] | |
| ) | |
| gr.HTML(""" | |
| <div class="tips-box"> | |
| <p>πΈ <strong>Supported chart types:</strong></p> | |
| <p>β Bar, Line, Pie, Scatter</p> | |
| <p>β Histogram, Heatmap</p> | |
| <p>β Dashboard screenshots</p> | |
| <p>β Any data visualization</p> | |
| </div> | |
| """) | |
| gr.HTML('</div>') | |
| # Right Column | |
| with gr.Column(scale=1): | |
| gr.HTML('<div class="result-card">') | |
| gr.HTML('<h3 style="color:#2575fc; margin:0 0 16px 0;">π Analysis Report</h3>') | |
| output = gr.Textbox( | |
| label="", | |
| lines=22, | |
| placeholder="Your analysis report will appear here...\n\nUpload a chart and click 'π Analyze Chart' to get started!", | |
| ) | |
| gr.HTML('</div>') | |
| # Footer | |
| gr.HTML(""" | |
| <div class="footer-note"> | |
| <p>β‘ <strong>ChartTalker</strong> β Built with Python, Gradio & AI Vision. | |
| Supports Bar, Line, Pie, Scatter, Histogram, Heatmap and more!</p> | |
| </div> | |
| """) | |
| analyze_btn.click( | |
| fn=analyze_chart, | |
| inputs=[image_input, analysis_type], | |
| outputs=output | |
| ) | |
| app.launch() |