AgentOp

Preview: Data Analysis Agent Template

AI-powered data analysis agent that can analyze CSV files, create visualizations, and provide insights using pandas, numpy, and matplotlib.

Preview Mode

This is a preview with sample data. The template uses placeholders like which will be replaced with actual agent data.

Template Preview

Template Metadata

Slug
data-analysis-agent-template
Created By
ozzo
Created
Oct 27, 2025
Usage Count
12

Tags

data-analysis pandas visualization csv statistics

Code Statistics

HTML Lines
140
CSS Lines
312
JS Lines
322
Python Lines
199

Source Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>{{ agent_name }} — AI-Powered Data Analysis</title>
  
  <style>
    {{ css_code }}
  </style>

  <!-- Conditional Script Imports -->
  {% if needs_pyodide %}
  <script src="https://cdn.jsdelivr.net/pyodide/v{{ pyodide_version }}/full/pyodide.js"></script>
  {% endif %}
  
  <script>
    const PROVIDER = "{{ embedded_provider }}";
    const API_KEY = "{{ embedded_api_key }}";
    const AGENT_CONFIG = {{ default_config|safe }};
    const NEEDS_PYODIDE = {{ needs_pyodide|lower }};
    const PYODIDE_VERSION = "{{ pyodide_version }}";
  </script>
</head>
<body>
  <div class="dashboard">
    <div class="sidebar">
      <div class="agent-header">
        <div class="agent-icon">📊</div>
        <div>
          <h2 style="margin: 0; font-size: 1.1rem;">Data Analysis Agent</h2>
          <p style="margin: 0; color: #64748b; font-size: 0.875rem;">Data Analysis</p>
        </div>
      </div>
      
      <h3>Data Upload</h3>
      <div class="input-section" style="margin-bottom: 2rem;">
        <div class="upload-area" id="file-upload-area" 
             ondragenter="handleDragOver(event)"
             ondragover="handleDragOver(event)" 
             ondragleave="handleDragLeave(event)" 
             ondrop="handleDrop(event)"
             onclick="document.getElementById('csv-file-input').click()">
          <div style="color: var(--text-muted);">
            <div style="font-size: 2rem; margin-bottom: 0.5rem;">📁</div>
            <p style="margin: 0; font-weight: 500;">Drop CSV file here</p>
            <p style="margin: 0.25rem 0 0 0; font-size: 0.75rem;">or click to browse</p>
          </div>
          <input type="file" id="csv-file-input" accept=".csv,.txt" style="display: none;" onchange="handleFileSelect(event)">
        </div>
        
        <div id="file-info" class="file-info" style="display: none;">
          <div style="display: flex; justify-content: space-between; align-items: center;">
            <div>
              <div style="font-weight: 500;" id="fileName">data.csv</div>
              <div style="font-size: 0.75rem; color: #6b7280;" id="fileSize">1.2 KB</div>
            </div>
            <button onclick="clearFile()" class="btn" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">Clear</button>
          </div>
        </div>
      </div>

      <!-- Data Preview Section -->
      <div id="data-preview" style="display: none; margin-top: 1.5rem;">
        <h3>Data Preview</h3>
        <div id="preview-content" style="
            border: 1px solid var(--surface-border);
            border-radius: 8px;
            padding: 1rem;
            background: var(--surface-bg);
            overflow-x: auto;
            max-height: 300px;
            overflow-y: auto;
        ">
          <div id="preview-table"></div>
        </div>
      </div>

      <h3>Analysis Query</h3>
      <div class="input-section">
        <textarea id="queryInput" 
                 class="query-input"
                 placeholder="Examples:
â€ĸ Plot a histogram of sales values
â€ĸ Show correlation between variables
â€ĸ What are the summary statistics?
â€ĸ Create a chart for the region column"
          rows="4">
        </textarea>
        
        <button class="analyze-button" id="analyzeBtn" onclick="processQuery()">
          <span>🔍</span>
          <span>Analyze Data</span>
        </button>
      </div>
      
      <div class="sample-queries">
        <h4 style="margin-bottom: 0.75rem; font-size: 0.9rem; color: var(--text-heading);">Sample Queries</h4>
        <div class="sample-query" onclick="fillSampleQuery('Show me a summary of the data')">📊 Show me a summary of the data</div>
        <div class="sample-query" onclick="fillSampleQuery('Create a histogram of sales')">📈 Create a histogram of sales</div>
        <div class="sample-query" onclick="fillSampleQuery('What are the correlations between numeric columns?')">📋 What are the correlations between numeric columns?</div>
        <div class="sample-query" onclick="fillSampleQuery('Show value counts for the region column')">🔗 Show value counts for the region column</div>
      </div>
    </div>
    
    <div class="main-content">
      <div class="agent-header">
        <h1 style="margin: 0; font-size: 1.5rem; color: var(--text-heading);">Data Analysis Results</h1>
        <div style="margin-left: auto; font-size: 0.875rem; color: #64748b;">Powered by AI</div>
      </div>
      
      <div class="result-area">
        <div id="results-container" class="result-content">
          <div style="text-align: center; color: var(--text-muted); padding: 3rem 0;">
            <div style="font-size: 3rem; margin-bottom: 1rem;">📊</div>
            <h3 style="margin: 0 0 0.5rem 0;">Ready for Analysis</h3>
            <p style="margin: 0;">Upload a CSV file and ask questions about your data</p>
          </div>
        </div>
        
        <div class="loading-indicator" id="loading-indicator">
          <div class="spinner"></div>
          <p>Analyzing your data...</p>
        </div>
      </div>
    </div>
  </div>

<script>
{{ js_code|safe }}
</script>

<!-- Hidden Python code -->
<script type="text/python" id="python-code">
{{ python_code|safe }}
</script>

</body>
</html>
:root {
  --primary: #059669;
  --primary-hover: #047857;
  --secondary: #0ea5e9;
  --surface-bg: #f8fafc;
  --surface-border: #e2e8f0;
  --text-heading: #1e293b;
  --text-body: #475569;
  --text-muted: #64748b;
  --radius-lg: 12px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: linear-gradient(135deg, #f0f9ff 0%, #f8fafc 100%);
  color: var(--text-body);
  line-height: 1.6;
}

.dashboard {
  max-width: 1400px;
  margin: 0 auto;
  padding: 1.5rem;
  display: grid;
  grid-template-columns: 300px 1fr;
  gap: 1.5rem;
  min-height: 100vh;
}

.sidebar {
  background: white;
  border-radius: var(--radius-lg);
  padding: 1.5rem;
  height: fit-content;
  border: 1px solid var(--surface-border);
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}

.main-content {
  background: white;
  border-radius: var(--radius-lg);
  padding: 2rem;
  border: 1px solid var(--surface-border);
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
  position: relative;
}

.agent-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 2rem;
  padding-bottom: 1rem;
  border-bottom: 1px solid var(--surface-border);
}

.agent-icon {
  width: 48px;
  height: 48px;
  background: linear-gradient(135deg, var(--primary), var(--secondary));
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  font-size: 1.25rem;
}

.sidebar h3 {
  margin: 0 0 1rem 0;
  color: var(--text-heading);
  font-size: 1.1rem;
}

.upload-area {
  border: 2px dashed var(--surface-border);
  border-radius: 8px;
  padding: 2rem;
  text-align: center;
  cursor: pointer;
  transition: all 0.3s;
  margin-bottom: 1rem;
}

.upload-area:hover, .upload-area.drag-over {
  border-color: var(--primary);
  background: rgba(5, 150, 105, 0.05);
}

.file-info {
  display: none;
  padding: 0.75rem;
  background: #f0f9ff;
  border: 1px solid #bfdbfe;
  border-radius: 6px;
  margin-bottom: 0.75rem;
}

.query-input {
  width: 100%;
  min-height: 100px;
  max-height: 200px;
  padding: 0.75rem;
  border: 1px solid var(--surface-border);
  border-radius: 8px;
  font-family: inherit;
  font-size: 0.875rem;
  resize: vertical;
  margin-bottom: 0.75rem;
  outline: none;
  transition: border-color 0.2s;
  box-sizing: border-box;
}

.query-input:focus {
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(5, 150, 105, 0.1);
}

.analyze-button {
  width: 100%;
  background: var(--primary);
  color: white;
  padding: 0.75rem 1rem;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-weight: 500;
  transition: background-color 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}

.analyze-button:hover:not(:disabled) {
  background: var(--primary-hover);
}

.analyze-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.btn {
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 6px;
  cursor: pointer;
  font-weight: 500;
  transition: background-color 0.2s;
  background: var(--primary);
  color: white;
}

.btn:hover { background: var(--primary-hover); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }

.sample-queries {
  margin-top: 1.5rem;
}

.sample-query {
  background: #f8fafc;
  border: 1px solid var(--surface-border);
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.5rem;
  font-size: 0.875rem;
  cursor: pointer;
  transition: background-color 0.2s;
}

.sample-query:hover {
  background: #e2e8f0;
}

.result-area {
  min-height: 500px;
  background: var(--surface-bg);
  border-radius: 8px;
  padding: 1.5rem;
  margin-top: 1rem;
  border: 1px solid var(--surface-border);
  position: relative;
}

.result-content {
  line-height: 1.6;
}

.message {
  display: flex;
  gap: 1rem;
  margin-bottom: 1.5rem;
  padding: 1rem;
  border-radius: 8px;
  background: white;
  border: 1px solid var(--surface-border);
}

.message-user {
  background: #eff6ff;
  border-color: #bfdbfe;
}

.message-assistant {
  background: #f0fdf4;
  border-color: #bbf7d0;
}

.message-error {
  background: #fef2f2;
  border-color: #fecaca;
}

.message-system, .message-info {
  background: #fefce8;
  border-color: #fde047;
}

.message-icon {
  font-size: 1.5rem;
  flex-shrink: 0;
}

.message-content {
  flex: 1;
  overflow-wrap: break-word;
}

.result-content h1, .result-content h2, .result-content h3 {
  color: var(--text-heading);
  margin-top: 1.5rem;
  margin-bottom: 0.75rem;
}

.result-content img {
  max-width: 100%;
  height: auto;
  margin: 1rem 0;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}

.loading-indicator {
  display: none;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  color: #64748b;
}

.loading-indicator.show {
  display: block;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #e2e8f0;
  border-top: 3px solid var(--primary);
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin: 0 auto 1rem;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

table {
  border-collapse: collapse;
  width: 100%;
  margin: 1rem 0;
  font-family: monospace;
  font-size: 0.9rem;
  background: white;
  border-radius: 8px;
  overflow: hidden;
}

th, td {
  border: 1px solid var(--surface-border);
  padding: 0.5rem;
  text-align: left;
}

th {
  background: var(--surface-bg);
  font-weight: 600;
  color: var(--text-heading);
}

tr:nth-child(even) {
  background: #f8fafc;
}

@media (max-width: 768px) {
  .dashboard {
    grid-template-columns: 1fr;
    gap: 1rem;
    padding: 1rem;
  }
}
// File Upload Handlers
function handleDragOver(event) {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'copy';
    document.getElementById('file-upload-area').classList.add('drag-over');
}

function handleDragLeave(event) {
    event.preventDefault();
    document.getElementById('file-upload-area').classList.remove('drag-over');
}

function handleDrop(event) {
    event.preventDefault();
    document.getElementById('file-upload-area').classList.remove('drag-over');
    
    const files = event.dataTransfer.files;
    if (files.length > 0) {
        handleFile(files[0]);
    }
}

function handleFileSelect(event) {
    const file = event.target.files[0];
    if (file) {
        handleFile(file);
    }
}

async function handleFile(file) {
    const fileInfo = document.getElementById('file-info');
    const fileName = document.getElementById('fileName') || document.querySelector('#file-info .file-name');
    const fileSize = document.getElementById('fileSize') || document.querySelector('#file-info .file-size');
    const dataPreview = document.getElementById('data-preview');
    
    if (fileName) fileName.textContent = file.name;
    if (fileSize) fileSize.textContent = formatFileSize(file.size);
    if (fileInfo) fileInfo.style.display = 'block';
    
    // Wait for Pyodide to be ready
    let attempts = 0;
    const maxAttempts = 150; // 30 seconds (150 * 200ms)
    while (!window.pyodide && attempts < maxAttempts) {
        await new Promise(resolve => setTimeout(resolve, 200));
        attempts++;
    }
    
    if (!window.pyodide) {
        console.error('Pyodide not initialized after 30 seconds');
        return;
    }
    
    // Read and preview the file
    const reader = new FileReader();
    reader.onload = async function(e) {
        const csvContent = e.target.result;
        displayDataPreview(csvContent);
        if (dataPreview) dataPreview.style.display = 'block';
        
        // Load data into Python for analysis
        try {
            window.pyodide.globals.set('csv_content', csvContent);
            window.pyodide.globals.set('filename', file.name);
            const result = await window.pyodide.runPythonAsync('load_csv_data(csv_content, filename)');
            console.log('✅ CSV data loaded into Python:', result);
        } catch (error) {
            console.error('❌ Failed to load CSV into Python:', error);
        }
    };
    reader.readAsText(file);
}

function displayDataPreview(csvContent) {
    const lines = csvContent.split('\n').filter(line => line.trim());
    const previewTable = document.getElementById('preview-table');
    
    if (!previewTable || lines.length === 0) return;
    
    const maxRows = Math.min(lines.length, 10); // Show first 10 rows
    let tableHTML = '<table style="width: 100%; border-collapse: collapse; font-size: 0.875rem;">';
    
    for (let i = 0; i < maxRows; i++) {
        const cells = lines[i].split(',');
        const isHeader = i === 0;
        const tag = isHeader ? 'th' : 'td';
        const style = isHeader ? 
            'border: 1px solid var(--surface-border); padding: 0.5rem; background: var(--surface-bg); font-weight: 600;' :
            'border: 1px solid var(--surface-border); padding: 0.5rem;';
            
        tableHTML += '<tr>';
        cells.forEach(cell => {
            tableHTML += `<${tag} style="${style}">${cell.trim()}</${tag}>`;
        });
        tableHTML += '</tr>';
    }
    
    if (lines.length > 10) {
        tableHTML += '<tr><td colspan="100%" style="border: 1px solid var(--surface-border); padding: 0.5rem; text-align: center; font-style: italic; color: var(--text-muted);">... and ' + (lines.length - 10) + ' more rows</td></tr>';
    }
    
    tableHTML += '</table>';
    previewTable.innerHTML = tableHTML;
}

function formatFileSize(bytes) {
    if (bytes === 0) return '0 Bytes';
    const k = 1024;
    const sizes = ['Bytes', 'KB', 'MB', 'GB'];
    const i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}

function clearFile() {
    const fileInput = document.getElementById('csv-file-input');
    const fileInfo = document.getElementById('file-info');
    const dataPreview = document.getElementById('data-preview');
    
    if (fileInput) fileInput.value = '';
    if (fileInfo) fileInfo.style.display = 'none';
    if (dataPreview) dataPreview.style.display = 'none';
}

// Global variables
let pyodide = null;
let current_api_key = '';

// Load dataset into Python
async function loadDataset(csvContent, filename) {
    if (!window.pyodide) {
        addMessage('error', 'Python environment not ready. Please wait...');
        return;
    }
    
    try {
        addMessage('system', `Loading ${filename}...`);
        
        // Call Python function to load data
        window.pyodide.globals.set('csv_content', csvContent);
        window.pyodide.globals.set('filename', filename);
        
        const result = await window.pyodide.runPythonAsync(`
            load_csv_data(csv_content, filename)
        `);
        
        addMessage('system', result);
        
        // Show quick info about the dataset
        const info = await window.pyodide.runPython(`
            f"Dataset shape: {current_data.shape if current_data is not None else 'No data'}"
        `);
        addMessage('info', info);
        
    } catch (error) {
        addMessage('error', `Error loading dataset: ${error.message}`);
    }
}

// Query processing
async function processQuery() {
    const input = document.getElementById('queryInput');
    const query = input.value.trim();
    
    if (!query) return;
    
    console.log('[QUERY START]', query, new Date().toLocaleTimeString());
    const startTime = Date.now();
    
    if (!window.pyodide) {
        addMessage('error', 'Python environment not ready. Please wait...');
        return;
    }
    
    // Clear input and add user message
    input.value = '';
    addMessage('user', query);
    
    // Show loading indicator and disable button
    const loadingEl = document.getElementById('loading-indicator');
    const analyzeBtn = document.getElementById('analyzeBtn');
    
    if (loadingEl) loadingEl.classList.add('show');
    if (analyzeBtn) {
        analyzeBtn.disabled = true;
        analyzeBtn.innerHTML = '<span>âŗ</span><span>Processing...</span>';
    }
    
    try {
        console.log('[PYODIDE] Setting up query execution');
        
        // Set API key for this query
        if (window.API_KEY) {
            window.pyodide.globals.set('current_api_key', window.API_KEY);
            console.log('[PYODIDE] API key set, length:', window.API_KEY.length);
        }
        
        // Process query in Python
        window.pyodide.globals.set('user_query', query);
        console.log('[PYODIDE] Calling process_user_query()');
        
        const result = await window.pyodide.runPythonAsync(`process_user_query(user_query)`);
        
        console.log('[PYODIDE] Query completed, result length:', result ? result.length : 0);
        addMessage('assistant', result);
        
    } catch (error) {
        console.error('[ERROR] Query processing failed:', error);
        addMessage('error', `Error processing query: ${error.message}`);
    } finally {
        const duration = Date.now() - startTime;
        console.log('[QUERY END] Duration:', duration + 'ms');
        
        if (loadingEl) loadingEl.classList.remove('show');
        if (analyzeBtn) {
            analyzeBtn.disabled = false;
            analyzeBtn.innerHTML = '<span>🔍</span><span>Analyze Data</span>';
        }
    }
}

// Sample query functions
function fillSampleQuery(query) {
    const textarea = document.getElementById('queryInput');
    textarea.value = query;
    textarea.focus();
    // Don't auto-submit - let user review and click button
}

// Message display
function addMessage(type, content) {
    const resultsContainer = document.getElementById('results-container');
    const messageEl = document.createElement('div');
    messageEl.className = `message message-${type}`;
    
    // Preserve image tags by replacing them with placeholders
    const imgPlaceholders = [];
    let contentWithPlaceholders = content.replace(/<img[^>]*>/g, (match) => {
        const placeholder = `___IMG_PLACEHOLDER_${imgPlaceholders.length}___`;
        imgPlaceholders.push(match);
        return placeholder;
    });
    
    // Handle markdown-like formatting
    let formattedContent = contentWithPlaceholders
        .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
        .replace(/\*(.*?)\*/g, '<em>$1</em>')
        .replace(/### (.*?)(\n|$)/g, '<h3>$1</h3>')
        .replace(/## (.*?)(\n|$)/g, '<h2>$1</h2>')
        .replace(/# (.*?)(\n|$)/g, '<h1>$1</h1>')
        .replace(/^- (.*?)$/gm, '<li>$1</li>')
        .replace(/^â€ĸ (.*?)$/gm, '<li>$1</li>')
        .replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
    
    // Convert markdown tables to HTML
    formattedContent = formattedContent.replace(
        /(\|[^\n]+\|\n)(\|[-:\s|]+\|\n)((?:\|[^\n]+\|\n?)+)/g,
        (match, headerRow, separatorRow, bodyRows) => {
            // Parse header
            const headers = headerRow.split('|').filter(h => h.trim()).map(h => h.trim());
            
            // Parse body rows
            const rows = bodyRows.trim().split('\n').map(row => 
                row.split('|').filter(c => c.trim()).map(c => c.trim())
            );
            
            // Build HTML table
            let tableHtml = '<table style="width: 100%; border-collapse: collapse; margin: 1rem 0; font-size: 0.875rem;">';
            
            // Header
            tableHtml += '<thead><tr>';
            headers.forEach(h => {
                tableHtml += `<th style="border: 1px solid var(--surface-border); padding: 0.5rem; background: var(--surface-bg); font-weight: 600; text-align: left;">${h}</th>`;
            });
            tableHtml += '</tr></thead>';
            
            // Body
            tableHtml += '<tbody>';
            rows.forEach((row, idx) => {
                const bgStyle = idx % 2 === 0 ? 'background: white;' : 'background: #f8fafc;';
                tableHtml += `<tr style="${bgStyle}">`;
                row.forEach(cell => {
                    tableHtml += `<td style="border: 1px solid var(--surface-border); padding: 0.5rem;">${cell}</td>`;
                });
                tableHtml += '</tr>';
            });
            tableHtml += '</tbody></table>';
            
            return tableHtml;
        }
    );
    
    // Replace newlines with <br> (after table processing)
    formattedContent = formattedContent.replace(/\n/g, '<br>');
    
    // Restore image tags
    imgPlaceholders.forEach((img, index) => {
        formattedContent = formattedContent.replace(`___IMG_PLACEHOLDER_${index}___`, img);
    });
    
    messageEl.innerHTML = `
        <div class="message-icon">
            ${type === 'user' ? '👤' : type === 'assistant' ? '🤖' : type === 'error' ? '❌' : type === 'system' ? '🔧' : 'â„šī¸'}
        </div>
        <div class="message-content">${formattedContent}</div>
    `;
    
    resultsContainer.appendChild(messageEl);
    resultsContainer.scrollTop = resultsContainer.scrollHeight;
}

// Enter key handling
document.addEventListener('DOMContentLoaded', function() {
    // Enter key for query
    const queryInput = document.getElementById('queryInput');
    if (queryInput) {
        queryInput.addEventListener('keypress', function(e) {
            if (e.key === 'Enter' && !e.shiftKey) {
                e.preventDefault();
                processQuery();
            }
        });
    }
});
import pandas as pd
import numpy as np

# Template Variables - Users can customize these
max_rows = [[[MAX_ROWS|1000]]]
precision = [[[PRECISION|2]]]
chart_type = "[[[CHART_TYPE|bar]]]"
include_summary = [[[INCLUDE_SUMMARY|True]]]

# Global variables to store loaded data
current_data = None
current_filename = None

def dataframe_to_markdown(df, max_rows=10):
    """Convert pandas DataFrame to markdown table format."""
    if len(df) > max_rows:
        df = df.head(max_rows)
        truncated = True
    else:
        truncated = False
    lines = []
    headers = [''] + list(df.columns)
    lines.append('| ' + ' | '.join(str(h) for h in headers) + ' |')
    lines.append('|' + '|'.join([' --- ' for _ in range(len(headers))]) + '|')
    for idx, row in df.iterrows():
        row_values = [str(idx)] + [str(v) for v in row]
        lines.append('| ' + ' | '.join(row_values) + ' |')
    if truncated:
        lines.append(f'\n*Showing first {max_rows} rows of {len(df)} total*')
    return '\n'.join(lines)

def load_csv_data(csv_content: str, filename: str = "data.csv"):
    """Load CSV data into global variable."""
    global current_data, current_filename
    try:
        from io import StringIO
        current_data = pd.read_csv(StringIO(csv_content))
        current_filename = filename
        return f"✅ Loaded {current_data.shape[0]} rows and {current_data.shape[1]} columns from {filename}"
    except Exception as e:
        return f"❌ Error loading CSV: {str(e)}"

def get_data_summary() -> str:
    """Get dataset summary: shape, columns, data types, statistics."""
    global current_data
    if current_data is None:
        return "❌ No data loaded. Please upload a CSV file first."
    result = []
    result.append(f"## Dataset Overview")
    result.append(f"Shape: {current_data.shape[0]} rows × {current_data.shape[1]} columns")
    result.append(f"\nColumns: {', '.join(current_data.columns.tolist())}")
    result.append("\n### Data Types:")
    for col in current_data.columns:
        dtype = str(current_data[col].dtype)
        result.append(f"- **{col}**: {dtype}")
    result.append("\n### Missing Values:")
    missing = current_data.isnull().sum()
    has_missing = False
    for col in current_data.columns:
        if missing[col] > 0:
            pct = (missing[col] / len(current_data)) * 100
            result.append(f"- **{col}**: {missing[col]} missing ({pct:.1f}%)")
            has_missing = True
    if not has_missing:
        result.append("- ✅ No missing values found")
    if current_data.select_dtypes(include=[np.number]).shape[1] > 0:
        result.append("\n### Summary Statistics:")
        stats_df = current_data.describe()
        result.append(dataframe_to_markdown(stats_df))
    return "\n".join(result)

def get_column_info() -> str:
    """Get column info: data types and missing values."""
    global current_data
    if current_data is None:
        return "❌ No data loaded. Please upload a CSV file first."
    result = [f"## Column Information\n"]
    result.append(f"Dataset has **{len(current_data.columns)} columns** and **{len(current_data)} rows**:\n")
    result.append("| Column | Type | Non-Null | Missing | % Missing |")
    result.append("| --- | --- | --- | --- | --- |")
    for col in current_data.columns:
        dtype = str(current_data[col].dtype)
        non_null = current_data[col].count()
        total = len(current_data)
        missing = total - non_null
        pct_missing = (missing / total) * 100
        result.append(f"| {col} | {dtype} | {non_null} | {missing} | {pct_missing:.1f}% |")
    return "\n".join(result)

def get_value_counts(column: str) -> str:
    """Get value counts for a specific column."""
    global current_data
    if current_data is None:
        return "❌ No data loaded. Please upload a CSV file first."
    if column not in current_data.columns:
        return f"❌ Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
    result = [f"## Value counts for '{column}'\n"]
    value_counts = current_data[column].value_counts().head(15)
    total = len(current_data)
    result.append("| Value | Count | Percentage |")
    result.append("| --- | --- | --- |")
    for value, count in value_counts.items():
        pct = (count / total) * 100
        result.append(f"| {value} | {count} | {pct:.1f}% |")
    unique_count = current_data[column].nunique()
    if unique_count > 15:
        result.append(f"\n*Showing top 15 of {unique_count} unique values*")
    else:
        result.append(f"\n*Total unique values: {unique_count}*")
    return "\n".join(result)

def create_chart(column: str, chart_type: str = "histogram") -> str:
    """Create a chart for a specific column."""
    global current_data
    if current_data is None:
        return "❌ No data loaded. Please upload a CSV file first."
    if column not in current_data.columns:
        return f"❌ Column '{column}' not found. Available columns: {', '.join(current_data.columns)}"
    try:
        try:
            import matplotlib
            matplotlib.use('Agg')  # CRITICAL: Use Agg backend for Pyodide
            import matplotlib.pyplot as plt
            import base64
            from io import BytesIO
            plt.ioff()
        except ImportError as e:
            return f"❌ Chart creation unavailable: {str(e)}"
        
        fig, ax = plt.subplots(figsize=(10, 6))
        
        if chart_type.lower() == "bar":
            value_counts = current_data[column].value_counts().head(10)
            ax.bar(range(len(value_counts)), value_counts.values, color='#059669')
            ax.set_xticks(range(len(value_counts)))
            ax.set_xticklabels(value_counts.index, rotation=45, ha='right')
            ax.set_ylabel('Count')
            ax.set_title(f'Bar Chart: {column}', fontsize=14, fontweight='bold')
            ax.grid(axis='y', alpha=0.3)
        elif chart_type.lower() == "histogram":
            if pd.api.types.is_numeric_dtype(current_data[column]):
                ax.hist(current_data[column].dropna(), bins=20, alpha=0.7, color='#059669', edgecolor='white')
                ax.set_xlabel(column)
                ax.set_ylabel('Frequency')
                ax.set_title(f'Histogram: {column}', fontsize=14, fontweight='bold')
                ax.grid(axis='y', alpha=0.3)
            else:
                plt.close(fig)
                return f"❌ Cannot create histogram for non-numeric column '{column}'. Try 'bar' chart instead."
        else:
            plt.close(fig)
            return f"❌ Unsupported chart type '{chart_type}'. Use: bar or histogram"
        
        plt.tight_layout()
        
        # Save to BytesIO buffer and encode to base64
        buffer = BytesIO()
        plt.savefig(buffer, format='png', dpi=100, bbox_inches='tight')
        buffer.seek(0)
        image_base64 = base64.b64encode(buffer.getvalue()).decode('utf-8')
        plt.close(fig)
        
        # Return HTML with embedded base64 image
        return f"""✅ Chart created successfully for '{column}' ({chart_type} chart).

<img src="data:image/png;base64,{image_base64}" alt="{chart_type.title()} chart for {column}" style="max-width: 100%; height: auto; margin: 10px 0; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">

Chart shows the distribution of values in the '{column}' column."""
    except Exception as e:
        import traceback
        error_details = traceback.format_exc()
        return f"❌ Error creating chart: {str(e)}\n\nDetails:\n{error_details}"

def get_correlation_analysis() -> str:
    """Get correlation analysis for numeric columns."""
    global current_data
    if current_data is None:
        return "❌ No data loaded. Please upload a CSV file first."
    numeric_cols = current_data.select_dtypes(include=[np.number]).columns
    if len(numeric_cols) < 2:
        return "❌ Need at least 2 numerical columns to calculate correlations."
    corr_matrix = current_data[numeric_cols].corr()
    result = ["## Correlation Analysis\n"]
    result.append("### Correlation Matrix:\n")
    result.append(dataframe_to_markdown(corr_matrix, max_rows=20))
    result.append("\n### Key Insights:")
    strong_corr = []
    for i in range(len(numeric_cols)):
        for j in range(i+1, len(numeric_cols)):
            corr_val = corr_matrix.iloc[i, j]
            if abs(corr_val) > 0.7:
                strength = "strong positive" if corr_val > 0 else "strong negative"
                emoji = "📈" if corr_val > 0 else "📉"
                strong_corr.append(f"- {emoji} **{numeric_cols[i]}** and **{numeric_cols[j]}**: {strength} correlation ({corr_val:.3f})")
    if strong_corr:
        result.extend(strong_corr)
    else:
        result.append("- â„šī¸ No strong correlations found (|r| > 0.7)")
    return "\n".join(result)