store images
Browse files- app.py +46 -46
- storage.py +212 -0
app.py
CHANGED
@@ -6,49 +6,35 @@ import io
|
|
6 |
import base64
|
7 |
import logging
|
8 |
import os
|
9 |
-
import
|
10 |
-
import
|
11 |
-
import
|
12 |
-
|
|
|
|
|
13 |
|
14 |
# Configure logging
|
15 |
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
|
16 |
logger = logging.getLogger(__name__)
|
17 |
|
18 |
-
# Configure
|
19 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
20 |
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
genai.configure(api_key=GEMINI_API_KEY)
|
23 |
gemini_model = genai.GenerativeModel('gemini-2.0-flash-exp')
|
24 |
|
25 |
-
VOTES_FILE = "votes.json"
|
26 |
-
votes = []
|
27 |
-
votes_lock = threading.Lock()
|
28 |
-
|
29 |
-
def load_votes():
|
30 |
-
global votes
|
31 |
-
if os.path.exists(VOTES_FILE):
|
32 |
-
with open(VOTES_FILE, "r") as f:
|
33 |
-
votes = json.load(f)
|
34 |
-
else:
|
35 |
-
votes = []
|
36 |
-
|
37 |
-
def save_votes():
|
38 |
-
with votes_lock:
|
39 |
-
with open(VOTES_FILE, "w") as f:
|
40 |
-
json.dump(votes, f, indent=2)
|
41 |
-
|
42 |
-
def periodic_vote_dump(interval=60):
|
43 |
-
def run():
|
44 |
-
while True:
|
45 |
-
save_votes()
|
46 |
-
time.sleep(interval)
|
47 |
-
t = threading.Thread(target=run, daemon=True)
|
48 |
-
t.start()
|
49 |
-
|
50 |
-
load_votes()
|
51 |
-
periodic_vote_dump(60)
|
52 |
|
53 |
def get_default_username(profile: gr.OAuthProfile | None) -> str:
|
54 |
"""
|
@@ -122,9 +108,37 @@ def process_image(image):
|
|
122 |
return "Please upload an image.", "Please upload an image."
|
123 |
|
124 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
gemini_result = gemini_ocr(image)
|
126 |
mistral_result = mistral_ocr(image)
|
127 |
return gemini_result, mistral_result
|
|
|
128 |
except Exception as e:
|
129 |
logger.error(f"Error processing image: {e}")
|
130 |
return f"Error processing image: {e}", f"Error processing image: {e}"
|
@@ -184,13 +198,6 @@ with gr.Blocks(title="OCR Comparison: Gemini vs Mistral", css="""
|
|
184 |
if not username:
|
185 |
gr.Info("Please login with your Hugging Face account to vote.")
|
186 |
return
|
187 |
-
image_id = get_image_id(image)
|
188 |
-
with votes_lock:
|
189 |
-
for v in votes:
|
190 |
-
if v["image_id"] == image_id and v["username"] == username:
|
191 |
-
gr.Info("You already voted for this image.")
|
192 |
-
return
|
193 |
-
votes.append({"image_id": image_id, "username": username, "winner": "gemini"})
|
194 |
info_message = (
|
195 |
f"<p>You voted for <strong style='color:green;'>👈 Gemini OCR</strong>.</p>"
|
196 |
f"<p><span style='color:green;'>👈 Gemini OCR</span> - "
|
@@ -202,13 +209,6 @@ with gr.Blocks(title="OCR Comparison: Gemini vs Mistral", css="""
|
|
202 |
if not username:
|
203 |
gr.Info("Please login with your Hugging Face account to vote.")
|
204 |
return
|
205 |
-
image_id = get_image_id(image)
|
206 |
-
with votes_lock:
|
207 |
-
for v in votes:
|
208 |
-
if v["image_id"] == image_id and v["username"] == username:
|
209 |
-
gr.Info("You already voted for this image.")
|
210 |
-
return
|
211 |
-
votes.append({"image_id": image_id, "username": username, "winner": "mistral"})
|
212 |
info_message = (
|
213 |
f"<p>You voted for <strong style='color:blue;'>👉 Mistral OCR</strong>.</p>"
|
214 |
f"<p><span style='color:green;'>👈 Gemini OCR</span> - "
|
|
|
6 |
import base64
|
7 |
import logging
|
8 |
import os
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
from storage import upload_file_to_bucket
|
11 |
+
import datetime
|
12 |
+
|
13 |
+
# Load environment variables from .env file
|
14 |
+
load_dotenv()
|
15 |
|
16 |
# Configure logging
|
17 |
logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s')
|
18 |
logger = logging.getLogger(__name__)
|
19 |
|
20 |
+
# Configure API keys from environment variables
|
21 |
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
22 |
MISTRAL_API_KEY = os.getenv("MISTRAL_API_KEY")
|
23 |
|
24 |
+
# Log API key status (without exposing the actual keys)
|
25 |
+
if GEMINI_API_KEY:
|
26 |
+
logger.info("✅ GEMINI_API_KEY loaded successfully")
|
27 |
+
else:
|
28 |
+
logger.error("❌ GEMINI_API_KEY not found in environment variables")
|
29 |
+
|
30 |
+
if MISTRAL_API_KEY:
|
31 |
+
logger.info("✅ MISTRAL_API_KEY loaded successfully")
|
32 |
+
else:
|
33 |
+
logger.error("❌ MISTRAL_API_KEY not found in environment variables")
|
34 |
+
|
35 |
genai.configure(api_key=GEMINI_API_KEY)
|
36 |
gemini_model = genai.GenerativeModel('gemini-2.0-flash-exp')
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
|
39 |
def get_default_username(profile: gr.OAuthProfile | None) -> str:
|
40 |
"""
|
|
|
108 |
return "Please upload an image.", "Please upload an image."
|
109 |
|
110 |
try:
|
111 |
+
# Save the PIL image to a temporary file
|
112 |
+
temp_filename = f"temp_image_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
113 |
+
image.save(temp_filename)
|
114 |
+
|
115 |
+
# Upload the temporary file to Supabase storage
|
116 |
+
logger.info(f"📤 Uploading image to Supabase storage: {temp_filename}")
|
117 |
+
upload_result = upload_file_to_bucket(
|
118 |
+
file_path=temp_filename,
|
119 |
+
bucket_name="images",
|
120 |
+
storage_path=f"ocr_images/{temp_filename}",
|
121 |
+
file_options={"cache-control": "3600", "upsert": "false"}
|
122 |
+
)
|
123 |
+
|
124 |
+
if upload_result["success"]:
|
125 |
+
logger.info(f"✅ Image uploaded successfully: {upload_result['storage_path']}")
|
126 |
+
logger.info(f"🔗 Public URL: {upload_result['public_url']}")
|
127 |
+
else:
|
128 |
+
logger.error(f"❌ Image upload failed: {upload_result['error']}")
|
129 |
+
|
130 |
+
# Clean up temporary file
|
131 |
+
try:
|
132 |
+
os.remove(temp_filename)
|
133 |
+
logger.info(f"🗑️ Cleaned up temporary file: {temp_filename}")
|
134 |
+
except Exception as e:
|
135 |
+
logger.warning(f"⚠️ Could not remove temporary file {temp_filename}: {e}")
|
136 |
+
|
137 |
+
# Process OCR
|
138 |
gemini_result = gemini_ocr(image)
|
139 |
mistral_result = mistral_ocr(image)
|
140 |
return gemini_result, mistral_result
|
141 |
+
|
142 |
except Exception as e:
|
143 |
logger.error(f"Error processing image: {e}")
|
144 |
return f"Error processing image: {e}", f"Error processing image: {e}"
|
|
|
198 |
if not username:
|
199 |
gr.Info("Please login with your Hugging Face account to vote.")
|
200 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
201 |
info_message = (
|
202 |
f"<p>You voted for <strong style='color:green;'>👈 Gemini OCR</strong>.</p>"
|
203 |
f"<p><span style='color:green;'>👈 Gemini OCR</span> - "
|
|
|
209 |
if not username:
|
210 |
gr.Info("Please login with your Hugging Face account to vote.")
|
211 |
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
212 |
info_message = (
|
213 |
f"<p>You voted for <strong style='color:blue;'>👉 Mistral OCR</strong>.</p>"
|
214 |
f"<p><span style='color:green;'>👈 Gemini OCR</span> - "
|
storage.py
ADDED
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Supabase storage helper for uploading files.
|
2 |
+
|
3 |
+
This module provides functions to upload files to Supabase storage
|
4 |
+
using HTTP requests, avoiding the dependency issues with the supabase client library.
|
5 |
+
"""
|
6 |
+
import os
|
7 |
+
import requests
|
8 |
+
import logging
|
9 |
+
from typing import Optional, Dict, Any
|
10 |
+
from pathlib import Path
|
11 |
+
|
12 |
+
# Import Supabase credentials from environment variables
|
13 |
+
from dotenv import load_dotenv
|
14 |
+
load_dotenv()
|
15 |
+
|
16 |
+
SUPABASE_URL = os.getenv("SUPABASE_URL")
|
17 |
+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
|
18 |
+
|
19 |
+
if not SUPABASE_URL or not SUPABASE_KEY:
|
20 |
+
raise ImportError("Could not load Supabase credentials from environment variables")
|
21 |
+
|
22 |
+
logger = logging.getLogger(__name__)
|
23 |
+
|
24 |
+
# Supabase storage API configuration
|
25 |
+
STORAGE_BASE_URL = f"{SUPABASE_URL}/storage/v1"
|
26 |
+
HEADERS = {
|
27 |
+
"apikey": SUPABASE_KEY,
|
28 |
+
"Authorization": f"Bearer {SUPABASE_KEY}",
|
29 |
+
"Content-Type": "application/json"
|
30 |
+
}
|
31 |
+
|
32 |
+
def upload_file_to_bucket(
|
33 |
+
file_path: str,
|
34 |
+
bucket_name: str = "images",
|
35 |
+
storage_path: Optional[str] = None,
|
36 |
+
file_options: Optional[Dict[str, Any]] = None
|
37 |
+
) -> Dict[str, Any]:
|
38 |
+
"""
|
39 |
+
Upload a file to Supabase storage bucket.
|
40 |
+
|
41 |
+
Args:
|
42 |
+
file_path: Local path to the file to upload
|
43 |
+
bucket_name: Name of the storage bucket (default: "images")
|
44 |
+
storage_path: Path in the bucket where to store the file (default: filename)
|
45 |
+
file_options: Optional file options like cache-control, upsert, etc.
|
46 |
+
|
47 |
+
Returns:
|
48 |
+
Dictionary with upload result information
|
49 |
+
"""
|
50 |
+
try:
|
51 |
+
# Check if file exists
|
52 |
+
if not os.path.exists(file_path):
|
53 |
+
raise FileNotFoundError(f"File not found: {file_path}")
|
54 |
+
|
55 |
+
# Get file info
|
56 |
+
file_path_obj = Path(file_path)
|
57 |
+
file_name = file_path_obj.name
|
58 |
+
|
59 |
+
# Use provided storage_path or default to filename
|
60 |
+
if storage_path is None:
|
61 |
+
storage_path = file_name
|
62 |
+
|
63 |
+
# Prepare upload URL
|
64 |
+
upload_url = f"{STORAGE_BASE_URL}/object/{bucket_name}/{storage_path}"
|
65 |
+
|
66 |
+
# Prepare headers for file upload
|
67 |
+
upload_headers = {
|
68 |
+
"apikey": SUPABASE_KEY,
|
69 |
+
"Authorization": f"Bearer {SUPABASE_KEY}"
|
70 |
+
}
|
71 |
+
|
72 |
+
# Add file options if provided
|
73 |
+
if file_options:
|
74 |
+
for key, value in file_options.items():
|
75 |
+
upload_headers[f"x-upsert"] = str(value).lower() if key == "upsert" else str(value)
|
76 |
+
|
77 |
+
# Read and upload file
|
78 |
+
with open(file_path, "rb") as f:
|
79 |
+
response = requests.post(
|
80 |
+
upload_url,
|
81 |
+
headers=upload_headers,
|
82 |
+
data=f
|
83 |
+
)
|
84 |
+
|
85 |
+
if response.status_code == 200:
|
86 |
+
result = response.json()
|
87 |
+
logger.info(f"✅ File uploaded successfully: {file_name}")
|
88 |
+
logger.info(f"📁 Storage path: {storage_path}")
|
89 |
+
logger.info(f"🔗 Public URL: {result.get('publicUrl', 'N/A')}")
|
90 |
+
return {
|
91 |
+
"success": True,
|
92 |
+
"file_name": file_name,
|
93 |
+
"storage_path": storage_path,
|
94 |
+
"public_url": result.get('publicUrl'),
|
95 |
+
"response": result
|
96 |
+
}
|
97 |
+
else:
|
98 |
+
logger.error(f"❌ Upload failed with status {response.status_code}")
|
99 |
+
logger.error(f"Response: {response.text}")
|
100 |
+
return {
|
101 |
+
"success": False,
|
102 |
+
"error": f"Upload failed: {response.status_code}",
|
103 |
+
"response": response.text
|
104 |
+
}
|
105 |
+
|
106 |
+
except Exception as e:
|
107 |
+
logger.error(f"❌ Error uploading file: {e}")
|
108 |
+
return {
|
109 |
+
"success": False,
|
110 |
+
"error": str(e)
|
111 |
+
}
|
112 |
+
|
113 |
+
def get_file_url(bucket_name: str, file_path: str) -> str:
|
114 |
+
"""
|
115 |
+
Get the public URL for a file in Supabase storage.
|
116 |
+
|
117 |
+
Args:
|
118 |
+
bucket_name: Name of the storage bucket
|
119 |
+
file_path: Path to the file in the bucket
|
120 |
+
|
121 |
+
Returns:
|
122 |
+
Public URL for the file
|
123 |
+
"""
|
124 |
+
return f"{SUPABASE_URL}/storage/v1/object/public/{bucket_name}/{file_path}"
|
125 |
+
|
126 |
+
def list_bucket_files(bucket_name: str = "images") -> Dict[str, Any]:
|
127 |
+
"""
|
128 |
+
List all files in a storage bucket.
|
129 |
+
|
130 |
+
Args:
|
131 |
+
bucket_name: Name of the storage bucket
|
132 |
+
|
133 |
+
Returns:
|
134 |
+
Dictionary with list of files
|
135 |
+
"""
|
136 |
+
try:
|
137 |
+
list_url = f"{STORAGE_BASE_URL}/object/list/{bucket_name}"
|
138 |
+
response = requests.get(list_url, headers=HEADERS)
|
139 |
+
|
140 |
+
if response.status_code == 200:
|
141 |
+
result = response.json()
|
142 |
+
logger.info(f"✅ Listed {len(result)} files in bucket '{bucket_name}'")
|
143 |
+
return {
|
144 |
+
"success": True,
|
145 |
+
"files": result
|
146 |
+
}
|
147 |
+
else:
|
148 |
+
logger.error(f"❌ Failed to list files: {response.status_code}")
|
149 |
+
return {
|
150 |
+
"success": False,
|
151 |
+
"error": f"Failed to list files: {response.status_code}"
|
152 |
+
}
|
153 |
+
except Exception as e:
|
154 |
+
logger.error(f"❌ Error listing files: {e}")
|
155 |
+
return {
|
156 |
+
"success": False,
|
157 |
+
"error": str(e)
|
158 |
+
}
|
159 |
+
|
160 |
+
def test_upload_image_png():
|
161 |
+
"""Test function to upload Image.png to the images bucket."""
|
162 |
+
try:
|
163 |
+
logger.info("🚀 Testing file upload to Supabase storage...")
|
164 |
+
|
165 |
+
# First, test if we can list files (this tests basic connectivity)
|
166 |
+
logger.info("📋 Testing bucket connectivity...")
|
167 |
+
list_result = list_bucket_files("images")
|
168 |
+
if list_result["success"]:
|
169 |
+
logger.info(f"✅ Bucket connectivity successful! Found {len(list_result['files'])} files")
|
170 |
+
else:
|
171 |
+
logger.warning(f"⚠️ Bucket connectivity issue: {list_result['error']}")
|
172 |
+
|
173 |
+
# Test upload
|
174 |
+
logger.info("📤 Testing file upload...")
|
175 |
+
result = upload_file_to_bucket(
|
176 |
+
file_path="Image.png",
|
177 |
+
bucket_name="images",
|
178 |
+
storage_path="test/Image.png",
|
179 |
+
file_options={"cache-control": "3600", "upsert": "false"}
|
180 |
+
)
|
181 |
+
|
182 |
+
if result["success"]:
|
183 |
+
logger.info("✅ Upload test successful!")
|
184 |
+
logger.info(f"📁 File uploaded to: {result['storage_path']}")
|
185 |
+
logger.info(f"🔗 Public URL: {result['public_url']}")
|
186 |
+
return True
|
187 |
+
else:
|
188 |
+
logger.error(f"❌ Upload test failed: {result['error']}")
|
189 |
+
|
190 |
+
# Provide helpful error information
|
191 |
+
if "row-level security policy" in result.get('response', ''):
|
192 |
+
logger.error("🔧 This is a storage permissions issue.")
|
193 |
+
logger.error("📋 To fix this, you need to configure your Supabase storage bucket:")
|
194 |
+
logger.error("""
|
195 |
+
1. Go to your Supabase dashboard
|
196 |
+
2. Navigate to Storage > Policies
|
197 |
+
3. For the 'images' bucket, add a policy like:
|
198 |
+
|
199 |
+
CREATE POLICY "Allow public uploads" ON storage.objects
|
200 |
+
FOR INSERT WITH CHECK (bucket_id = 'images');
|
201 |
+
|
202 |
+
CREATE POLICY "Allow public reads" ON storage.objects
|
203 |
+
FOR SELECT USING (bucket_id = 'images');
|
204 |
+
|
205 |
+
Or make the bucket public in Storage > Settings
|
206 |
+
""")
|
207 |
+
|
208 |
+
return False
|
209 |
+
|
210 |
+
except Exception as e:
|
211 |
+
logger.error(f"❌ Upload test error: {e}")
|
212 |
+
return False
|