diff --git a/.gitignore b/.gitignore index f7d3395..07fa505 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,4 @@ dataset/train/images dataset/train/labels app_logs.txt .aider* +*.tex \ No newline at end of file diff --git a/src/search_images.py b/src/search_images.py index a9476b6..40c6f4c 100644 --- a/src/search_images.py +++ b/src/search_images.py @@ -1,4 +1,5 @@ import requests +import json def search_images(query, api_key, search_engine_id, num_results=10): @@ -16,8 +17,8 @@ def search_images(query, api_key, search_engine_id, num_results=10): response = requests.get(search_url) if response.status_code != 200: - raise Exception( - f"Failed to fetch images: Status code {response.status_code}, Response: {response.text}") + error_message = _parse_api_error(response) + raise Exception(error_message) data = response.json() if 'items' not in data: @@ -33,3 +34,36 @@ def search_images(query, api_key, search_engine_id, num_results=10): break # No more results available return images + + +def _parse_api_error(response): + """Parse Google API error response and return a user-friendly message""" + try: + data = response.json() + if 'error' in data: + error_obj = data['error'] + + # Handle different error formats + if isinstance(error_obj, dict): + message = error_obj.get('message', 'Unknown error') + code = error_obj.get('code', response.status_code) + status = error_obj.get('status', 'UNKNOWN') + + # Map common errors to user-friendly messages + if status == 'PERMISSION_DENIED' or code == 403: + return f"Access Denied: {message} - Please check your Google API credentials and ensure the Custom Search JSON API is enabled in your Google Cloud project." + elif status == 'INVALID_ARGUMENT' or code == 400: + return f"Invalid Request: {message} - Please verify your search query and API configuration." + elif status == 'UNAUTHENTICATED' or code == 401: + return f"Authentication Failed: {message} - Your API key may be invalid or expired." + elif status == 'RESOURCE_EXHAUSTED' or code == 429: + return f"Rate Limited: {message} - You've exceeded your daily search quota. Please try again later." + else: + return f"API Error ({code}): {message}" + else: + return f"API Error: {str(error_obj)}" + except: + pass + + # Fallback error message + return f"Failed to fetch images: Status code {response.status_code}. The image search service returned an error. Please verify your API keys and search query." diff --git a/src/templates/base.html b/src/templates/base.html index 8f05a50..0c19813 100644 --- a/src/templates/base.html +++ b/src/templates/base.html @@ -4,6 +4,9 @@ {% block title %}SearchVision{% endblock %} + + +
-

🔍 SearchVision

+

SearchVision

Train your own object detection model in minutes

diff --git a/src/templates/error.html b/src/templates/error.html index 980b0ec..3fe1451 100644 --- a/src/templates/error.html +++ b/src/templates/error.html @@ -3,47 +3,168 @@ {% block title %}Error - SearchVision{% endblock %} {% block styles %} +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + .error-container { max-width: 600px; margin: 0 auto; text-align: center; + animation: fadeInDown 0.6s ease-out; + padding: 0 20px; } .error-icon { - font-size: 4rem; - margin-bottom: 20px; + width: 64px; + height: 64px; + margin: 0 auto 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 8px; + background: #fee2e2; +} + +.error-icon svg { + width: 36px; + height: 36px; + stroke: #991b1b; + stroke-width: 2; + fill: none; +} + +.error-container h2 { + font-size: 2rem; + font-weight: 700; + color: #020817; + margin-bottom: 12px; + letter-spacing: -0.5px; } .error-message { - background: #fee; - border: 1px solid #fcc; - border-radius: 12px; - padding: 24px; + background: #fee2e2; + border: 1px solid #fecaca; + border-radius: 8px; + padding: 20px; margin: 24px 0; text-align: left; } .error-message h3 { - color: #c33; - margin-bottom: 10px; + color: #991b1b; + margin-bottom: 12px; + font-size: 1rem; + font-weight: 600; } .error-message p { - color: #666; + color: #7f1d1d; word-break: break-word; + line-height: 1.6; + font-size: 0.95rem; +} + +.error-details { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 16px; + margin: 24px 0; + text-align: left; +} + +.error-details-title { + color: #020817; + font-weight: 600; + font-size: 0.9rem; + margin-bottom: 8px; +} + +.error-details-content { + color: #64748b; + font-size: 0.85rem; + font-family: 'Monaco', 'Courier New', monospace; + line-height: 1.4; + overflow-x: auto; +} + +.error-actions { + display: flex; + gap: 12px; + margin-top: 24px; + justify-content: center; +} + +.btn-link { + display: inline-block; + padding: 10px 24px; + background: #e2e8f0; + color: #020817; + border: 1px solid #cbd5e1; + border-radius: 6px; + font-size: 0.95rem; + font-weight: 500; + cursor: pointer; + text-decoration: none; + transition: all 0.2s ease; +} + +.btn-link:hover { + background: #cbd5e1; + border-color: #94a3b8; +} + +@media (max-width: 768px) { + .error-container h2 { + font-size: 1.5rem; + } + + .error-actions { + flex-direction: column; + } + + .btn-link { + width: 100%; + } } {% endblock %} {% block content %}
-
⚠️
+
+ + + + +

Something went wrong

-

Error Details:

+

Error Details

{{ error }}

- Try Again +
+
What you can do:
+
+ • Check your API keys and configuration
+ • Verify your internet connection
+ • Try a different search term
+ • Contact support if the issue persists +
+
+ +
{% endblock %} diff --git a/src/templates/search.html b/src/templates/search.html index 6673877..f986913 100644 --- a/src/templates/search.html +++ b/src/templates/search.html @@ -3,98 +3,382 @@ {% block title %}Search - SearchVision{% endblock %} {% block styles %} -.search-box { - max-width: 600px; - margin: 0 auto; - text-align: center; +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } } -.search-form { +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.error-alert { + animation: slideDown 0.3s ease-out; + background: #fee2e2; + border: 1px solid #fecaca; + border-radius: 8px; + padding: 16px; + margin-bottom: 24px; display: flex; gap: 12px; - margin-top: 30px; + align-items: flex-start; } -.search-input { +.error-alert-icon { + flex-shrink: 0; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 4px; + background: #fca5a5; +} + +.error-alert-icon svg { + width: 16px; + height: 16px; + stroke: #991b1b; + stroke-width: 2; + fill: none; +} + +.error-alert-content { flex: 1; - padding: 16px 20px; +} + +.error-alert-title { + color: #991b1b; + font-weight: 600; + font-size: 0.95rem; + margin-bottom: 4px; +} + +.error-alert-message { + color: #7f1d1d; + font-size: 0.9rem; + line-height: 1.4; +} + +.error-alert-close { + flex-shrink: 0; + background: none; + border: none; + color: #991b1b; + cursor: pointer; + padding: 4px; + display: flex; + align-items: center; + justify-content: center; + transition: opacity 0.2s; +} + +.error-alert-close:hover { + opacity: 0.7; +} + +.error-alert-close svg { + width: 16px; + height: 16px; + stroke: #991b1b; + stroke-width: 2; +} + +.hero-section { + width: 100%; + max-width: 900px; + text-align: center; + animation: fadeInDown 0.6s ease-out; + padding: 0 20px; +} + +.hero-section h2 { + font-size: 3.5rem; + font-weight: 700; + margin-bottom: 12px; + color: #020817; + letter-spacing: -1px; + line-height: 1.1; +} + +.hero-section > p { font-size: 1.1rem; - border: 2px solid #e0e0e0; + color: #64748b; + margin-bottom: 48px; + font-weight: 400; + opacity: 0.9; + animation: fadeInDown 0.6s ease-out 0.1s backwards; + line-height: 1.5; +} + +.search-box { + max-width: 700px; + margin: 0 auto 64px; + animation: fadeInUp 0.6s ease-out 0.2s backwards; + padding: 0 20px; +} + +.search-form { + display: flex; + gap: 8px; + margin-top: 0; + background: #ffffff; + padding: 4px; border-radius: 8px; + border: 1px solid #e2e8f0; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + transition: all 0.2s ease; +} + +.search-form:hover { + border-color: #cbd5e1; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); +} + +.search-form:focus-within { + border-color: #020817; + box-shadow: 0 0 0 3px rgba(2, 8, 23, 0.1), 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.search-input { + flex: 1; + padding: 12px 16px; + font-size: 1rem; + border: none; + border-radius: 6px; outline: none; - transition: border-color 0.3s; + background: transparent; + color: #020817; + transition: all 0.2s ease; + font-weight: 500; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} + +.search-input::placeholder { + color: #94a3b8; + font-weight: 400; } .search-input:focus { - border-color: #667eea; + outline: none; } .search-btn { - padding: 16px 32px; - font-size: 1.1rem; + padding: 12px 28px; + font-size: 0.95rem; + background: #020817; + color: #ffffff; + border: 1px solid #020817; + border-radius: 6px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); + white-space: nowrap; + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; +} + +.search-btn:hover { + background: #1e293b; + border-color: #1e293b; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.search-btn:active { + transform: scale(0.98); } .features { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 24px; - margin-top: 50px; + gap: 20px; + margin-top: 0; + animation: fadeInUp 0.6s ease-out 0.3s backwards; + padding: 0 20px; + max-width: 900px; + width: 100%; } .feature-card { padding: 24px; - background: #f8f9fa; - border-radius: 12px; + background: #ffffff; + border-radius: 8px; text-align: center; - transition: transform 0.2s; + transition: all 0.2s ease; + border: 1px solid #e2e8f0; + cursor: pointer; } .feature-card:hover { - transform: translateY(-4px); + border-color: #cbd5e1; + background: #f8fafc; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + transform: translateY(-2px); } .feature-icon { - font-size: 3rem; - margin-bottom: 16px; + width: 48px; + height: 48px; + margin: 0 auto 16px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 6px; + background: #f1f5f9; + transition: all 0.2s ease; +} + +.feature-icon svg { + width: 24px; + height: 24px; + stroke: #020817; + stroke-width: 2; + fill: none; +} + +.feature-card:hover .feature-icon { + background: #e2e8f0; } .feature-card h3 { - color: #333; + color: #020817; margin-bottom: 8px; + font-size: 1.05rem; + font-weight: 600; + letter-spacing: -0.3px; } .feature-card p { - color: #666; - font-size: 0.95rem; + color: #64748b; + font-size: 0.9rem; + line-height: 1.5; + font-weight: 400; +} + +@media (max-width: 768px) { + .hero-section h2 { + font-size: 2.2rem; + } + + .hero-section > p { + font-size: 1rem; + } + + .search-box { + margin-bottom: 48px; + } + + .search-form { + flex-direction: column; + } + + .search-input, + .search-btn { + width: 100%; + } + + .search-btn { + padding: 12px 20px; + } + + .features { + gap: 16px; + } + + .feature-card { + padding: 20px; + } + + .error-alert { + margin-bottom: 20px; + } } {% endblock %} {% block content %} -