File size: 8,069 Bytes
54809ad
 
 
 
 
a86e6ec
54809ad
 
 
 
 
a86e6ec
54809ad
 
 
 
 
 
 
0a0379d
54809ad
a86e6ec
 
54809ad
 
a86e6ec
 
 
 
 
 
 
 
 
 
 
 
 
 
54809ad
 
 
 
0a0379d
a86e6ec
54809ad
0a0379d
54809ad
a86e6ec
 
 
 
 
 
 
0a0379d
a86e6ec
 
 
0a0379d
a86e6ec
 
 
54809ad
a86e6ec
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54809ad
 
 
 
a86e6ec
54809ad
 
 
 
 
a86e6ec
54809ad
 
 
 
 
 
 
 
0a0379d
54809ad
 
 
 
 
 
a86e6ec
54809ad
a86e6ec
 
54809ad
 
 
a86e6ec
 
54809ad
 
 
 
a86e6ec
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import HTMLResponse
import pandas as pd
import io
import re
import os
from google import genai
from PIL import Image

app = FastAPI()

# 1. THE RAW HTML FRONTEND (Upgraded with JavaScript for dynamic loading)
@app.get("/")
def main_page():
    return HTMLResponse("""
    <!DOCTYPE html>
    <html>
    <head>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
        <title>PLC Reader</title>
        <style>
            body { font-family: system-ui, sans-serif; padding: 20px; background-color: #f8fafc; color: #0f172a; text-align: center; margin: 0;}
            .container { max-width: 600px; margin: auto; background: white; padding: 20px; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); }
            input[type="password"] { width: 100%; padding: 12px; margin-bottom: 20px; border: 1px solid #cbd5e1; border-radius: 6px; box-sizing: border-box; font-size: 16px;}
            input[type="file"] { display: none; }
            .camera-btn { display: block; background-color: #4338ca; color: white; padding: 15px; border-radius: 8px; font-size: 18px; font-weight: bold; cursor: pointer; border: none; width: 100%; box-sizing: border-box; margin-bottom: 20px;}
            
            /* New Styles for Image Preview and Loading Spinner */
            #preview-container { display: none; margin-bottom: 20px; }
            #preview-img { max-width: 100%; border-radius: 8px; border: 2px solid #e2e8f0; }
            .loader { display: none; font-weight: bold; color: #4338ca; margin: 20px 0; font-size: 1.1em;}
            .spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(67,56,202,.3); border-radius: 50%; border-top-color: #4338ca; animation: spin 1s ease-in-out infinite; vertical-align: middle; margin-right: 8px;}
            @keyframes spin { to { transform: rotate(360deg); } }
            
            /* Table Styles injected by backend */
            .table-container { overflow-x: auto; margin-top: 15px; text-align: left; }
            table { border-collapse: collapse; width: 100%; background: white; font-size: 14px;}
            th, td { border: 1px solid #e2e8f0; padding: 8px; white-space: nowrap; }
            th { background-color: #f1f5f9; }
        </style>
    </head>
    <body>
        <div class="container">
            <h2 style="margin-top:0; color: #1e293b;">🏭 PLC Data READER</h2>
            <p style="color: #64748b; margin-bottom: 25px;">Capture sinter plant log sheets to digitize tabular data.</p>
            
            <input type="password" id="api-key" placeholder="You are ready to go......">
            
            <label class="camera-btn" id="camera-label">
                <span id="btn-text">📸 Tap to Open Rear Camera</span>
                <input type="file" id="file-input" accept="image/*" capture="environment">
            </label>

            <div id="preview-container">
                <h4 style="margin-bottom: 10px; color: #475569; text-align: left;">Captured Image:</h4>
                <img id="preview-img" alt="Captured your image">
            </div>

            <div id="loading" class="loader">
                <span class="spinner"></span> Analyzing your image... this takes a few seconds.
            </div>

            <div id="results"></div>
        </div>

        <script>
            // This JavaScript handles the background processing so the page doesn't refresh
            document.getElementById('file-input').addEventListener('change', function(e) {
                const file = e.target.files[0];
                if (!file) return;

                // 1. Instantly display the image on the screen
                const reader = new FileReader();
                reader.onload = function(event) {
                    document.getElementById('preview-img').src = event.target.result;
                    document.getElementById('preview-container').style.display = 'block';
                };
                reader.readAsDataURL(file);

                // 2. Hide the camera button and show the loading spinner
                document.getElementById('camera-label').style.display = 'none';
                document.getElementById('results').innerHTML = ''; // Clear old tables
                document.getElementById('loading').style.display = 'block';

                // 3. Send the image to the Python backend in the background
                const formData = new FormData();
                formData.append('file', file);
                formData.append('api_key_input', document.getElementById('api-key').value);

                fetch('/extract', {
                    method: 'POST',
                    body: formData
                })
                .then(response => response.text())
                .then(html => {
                    // 4. Extraction complete! Hide loading, show tables, restore button
                    document.getElementById('loading').style.display = 'none';
                    document.getElementById('results').innerHTML = html;
                    
                    const camLabel = document.getElementById('camera-label');
                    camLabel.style.display = 'block';
                    document.getElementById('btn-text').innerText = '📸 Take Another Picture';
                })
                .catch(error => {
                    document.getElementById('loading').style.display = 'none';
                    document.getElementById('results').innerHTML = '<div style="color:#ef4444; padding:15px;"><b>❌ Connection Error</b><br>' + error + '</div>';
                    document.getElementById('camera-label').style.display = 'block';
                });
            });
        </script>
    </body>
    </html>
    """)

# 2. THE PYTHON BACKEND (Now only returns the formatted tables)
@app.post("/extract")
async def extract_data(api_key_input: str = Form(""), file: UploadFile = File(...)):
    try:
        api_key = os.environ.get("GEMINI_API_KEY") or api_key_input
        if not api_key:
            return HTMLResponse("<div style='padding:15px; background:#fef2f2; color:#991b1b; border-radius:8px;'><b>⚠️ Missing API Key.</b> Please enter it above.</div>")

        image_bytes = await file.read()
        img = Image.open(io.BytesIO(image_bytes))
        
        client = genai.Client(api_key=api_key)
        prompt = "Carefully extract all the data tables from this dashboard image. Format the output strictly as CSV. Enclose the CSV data inside ```csv ``` code blocks. Ensure column headers are included."
        
        response = client.models.generate_content(
            model="gemini-2.5-flash", 
            contents=[prompt, img]
        )
        
        csv_blocks = re.findall(r'```csv\n(.*?)\n```', response.text, re.DOTALL)
        
        if not csv_blocks:
            return HTMLResponse(f"<div style='padding:15px; background:#fffbeb; color:#92400e; border-radius:8px;'><b>⚠️ No tables found.</b> The model couldn't detect clear tables.<br><br>Raw Output:<br><pre style='white-space:pre-wrap; font-size:12px;'>{response.text}</pre></div>")
        
        # Build the HTML snippet for the tables
        html_output = f"<h3 style='color:#16a34a; margin-top:30px; border-top: 2px dashed #cbd5e1; padding-top:20px; text-align: left;'>✅ Extracted {len(csv_blocks)} Table(s)</h3>"
        
        for i, csv_data in enumerate(csv_blocks):
            df = pd.read_csv(io.StringIO(csv_data))
            html_output += f"<h4 style='color:#334155; margin-bottom:5px; margin-top: 20px; text-align:left;'>Table {i+1}</h4>"
            html_output += f"<div class='table-container'>{df.to_html(index=False, border=0)}</div>"
            
        return HTMLResponse(html_output)
        
    except Exception as e:
        return HTMLResponse(f"<div style='padding:15px; background:#fef2f2; color:#991b1b; border-radius:8px;'><b>❌ Error Processing Image:</b><br>{str(e)}</div>")