charttalker / app.py
tejashspeaks's picture
Update app.py
f024353 verified
Raw
History Blame Contribute Delete
9.92 kB
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()