mhd7music
Your descriptive commit message here
a6912c3
# Main Streamlit application file
import streamlit as st
from dotenv import load_dotenv
import os
import google.generativeai as genai
from PIL import Image
import io
import json
import logging
# Import helper functions and prompts
from utils import (
configure_gemini,
analyze_input_with_gemini,
resize_image,
MAX_IMAGE_DIMENSION,
MAX_IMAGE_MB
)
from prompts import DETAILED_DISCOVER_PROMPT
# Configure logging
logging.basicConfig(level=logging.INFO)
# --- Page Configuration ---
st.set_page_config(
page_title="Google Discover Optimizer",
page_icon="πŸ“°",
layout="wide",
)
# --- Load API Key ---
# Load local .env file if it exists
load_dotenv()
# Try to get API key from Streamlit secrets first, then environment variables
# On Hugging Face Spaces, secrets should be set in the Space settings.
api_key = st.secrets.get("GOOGLE_API_KEY") or os.getenv("GOOGLE_API_KEY")
# Configure Gemini API
gemini_model = configure_gemini(api_key)
# --- App Header ---
st.title("πŸ“° Google Discover Content Analyzer")
st.caption("Analyze news article screenshots or text to optimize for Google Discover visibility.")
# --- Input Selection --- #
input_type = st.radio(
"Select Input Type:",
('Screenshot Upload', 'Paste Text'),
horizontal=True,
key='input_type'
)
# --- Input Fields --- #
uploaded_file = None
article_text = ""
if input_type == 'Screenshot Upload':
uploaded_file = st.file_uploader(
"Upload Screenshot",
type=["png", "jpg", "jpeg", "webp"],
help=f"Upload a screenshot of the news article. Images will be resized to max {MAX_IMAGE_DIMENSION}px and compressed (target < {MAX_IMAGE_MB}MB)."
)
else:
article_text = st.text_area(
"Paste Article Text",
height=300,
placeholder="Paste the full text of the news article here...",
max_chars=15000, # Consistent with previous limit
help="Maximum ~2000-2500 words (15000 characters)."
)
# --- Analysis Button and Logic --- #
analyze_button = st.button("Analyze Content", type="primary")
# Initialize session state variables if they don't exist
if 'analysis_result' not in st.session_state:
st.session_state.analysis_result = None
if 'error_message' not in st.session_state:
st.session_state.error_message = None
if 'processed_image_bytes' not in st.session_state:
st.session_state.processed_image_bytes = None
if analyze_button:
st.session_state.analysis_result = None # Clear previous results
st.session_state.error_message = None
st.session_state.processed_image_bytes = None
image_bytes_for_analysis = None
input_provided = False
# --- Input Processing --- #
if input_type == 'Screenshot Upload' and uploaded_file is not None:
input_provided = True
try:
with st.spinner(f'Processing uploaded image ({uploaded_file.name})... Compress/Resize...'):
img_bytes = uploaded_file.getvalue()
logging.info(f"Original image size: {len(img_bytes) / (1024 * 1024):.2f} MB")
# Resize and compress the image using the helper function
processed_image_bytes, final_format = resize_image(img_bytes)
image_bytes_for_analysis = processed_image_bytes # Use processed for AI
st.session_state.processed_image_bytes = processed_image_bytes # Store for display
logging.info(f"Processed image size: {len(processed_image_bytes) / (1024 * 1024):.2f} MB, Format: {final_format}")
except Exception as e:
logging.error(f"Error processing image: {e}", exc_info=True)
st.session_state.error_message = f"Error processing image: {e}"
st.error(st.session_state.error_message)
elif input_type == 'Paste Text' and article_text.strip():
input_provided = True
logging.info(f"Processing text input (length: {len(article_text)} chars)")
# Text input doesn't need pre-processing here
else:
st.warning("Please provide input (upload a screenshot or paste text) before analyzing.")
# --- Gemini API Call --- #
if input_provided and not st.session_state.error_message:
if not gemini_model:
st.session_state.error_message = "Gemini API Key not configured. Please set the GOOGLE_API_KEY secret in your Space settings."
st.error(st.session_state.error_message)
else:
with st.spinner('Analyzing content with Gemini AI... This may take a moment...⏳'):
try:
analysis_result = analyze_input_with_gemini(
gemini_model=gemini_model,
prompt=DETAILED_DISCOVER_PROMPT,
image_bytes=image_bytes_for_analysis,
text_content=article_text if input_type == 'Paste Text' else None
)
st.session_state.analysis_result = analysis_result
st.success("Analysis complete! ✨")
except Exception as e:
logging.error(f"Error during Gemini analysis: {e}", exc_info=True)
st.session_state.error_message = f"Analysis failed: {e}"
st.error(st.session_state.error_message)
# --- Display Results --- #
if st.session_state.analysis_result:
result = st.session_state.analysis_result
logging.debug(f"Displaying results: {type(result)}")
st.divider()
st.header("πŸ“Š Analysis Results")
# Layout columns for image and score/details
col1, col2 = st.columns([1, 2]) # Adjust ratio as needed
with col1:
# Display image
if st.session_state.processed_image_bytes:
st.image(st.session_state.processed_image_bytes, caption="Processed Screenshot", use_column_width=True)
elif input_type == 'Paste Text':
st.info("Analysis based on text input.")
st.markdown("&nbsp;", unsafe_allow_html=True)
with col2:
# --- Display Error if present --- #
# Check if the result dictionary contains our specific error keys
is_error_result = isinstance(result, dict) and ('analysis_error' in result or 'raw_text' in result)
if is_error_result:
st.error(f"**Analysis Error:** {result.get('analysis_error', 'Unknown error')}")
raw_text = result.get('raw_text')
if raw_text:
with st.expander("Show Raw Gemini Output (for debugging)", expanded=False):
st.text(raw_text)
# Stop further rendering of normal results if there was an error
st.stop()
# --- Display Normal Results (if no error detected above) --- #
# Display Score prominently
if isinstance(result, dict) and 'google_discover_score' in result:
score_data = result.get('google_discover_score', {})
score = score_data.get('score')
explanation = score_data.get('explanation')
pos_factors = score_data.get('key_positive_factors', [])
neg_factors = score_data.get('key_negative_factors', [])
if score is not None:
try:
score_float = float(score)
st.metric(label="Estimated Google Discover Score", value=f"{score_float:.2f} / 1.00")
except (ValueError, TypeError):
st.metric(label="Estimated Google Discover Score", value=f"{score}")
logging.warning(f"Could not convert score '{score}' to float for formatting.")
if explanation:
st.caption(explanation)
if pos_factors:
st.success(f"πŸ‘ Key Strengths: {'; '.join(pos_factors)}")
if neg_factors:
st.warning(f"πŸ‘Ž Key Weaknesses: {'; '.join(neg_factors)}")
else:
st.warning("Score could not be calculated or found in the result.")
# Display Detailed Analysis Section
if isinstance(result, dict):
st.divider()
st.subheader("Detailed Analysis")
tab_keys = [k for k in result.keys() if k not in ['google_discover_score', 'input_type', 'analysis_error', 'raw_text']]
valid_tabs = {key: result.get(key) for key in tab_keys if result.get(key)}
tab_titles = [key.replace('_',' ').title() for key in valid_tabs.keys()]
if tab_titles:
tabs = st.tabs(tab_titles)
for i, key in enumerate(valid_tabs.keys()):
with tabs[i]:
section_data = valid_tabs[key]
if key == 'optimization_recommendations' and isinstance(section_data, list):
st.dataframe(section_data, use_container_width=True)
elif isinstance(section_data, (dict, list)):
st.json(section_data, expanded=True)
else:
st.write(section_data)
else:
st.info("No detailed analysis sections found.")
# Download Button
st.divider()
try:
# Make sure to dump the *original* result, not one potentially modified by error display
json_string = json.dumps(st.session_state.analysis_result, indent=2, ensure_ascii=False)
st.download_button(
label="Download Full Report (JSON)",
data=json_string,
file_name="discover_analysis_report.json",
mime="application/json",
)
except Exception as e:
st.warning(f"Could not generate JSON download: {e}")
# This case is now handled by the error check at the start of col2
# elif result:
# st.warning("Analysis did not return structured data. Displaying raw output:")
# st.text(str(result))
# Handle case where analysis button wasn't clicked, but an error exists from previous run
elif st.session_state.error_message:
st.error(st.session_state.error_message)
# Display initial warning if API key is missing
if not api_key:
st.warning("⚠️ Google API Key not found. Please set the `GOOGLE_API_KEY` secret in your Hugging Face Space settings for the analysis to work.", icon="🚨")