|
|
|
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 |
|
|
|
|
|
from utils import ( |
|
configure_gemini, |
|
analyze_input_with_gemini, |
|
resize_image, |
|
MAX_IMAGE_DIMENSION, |
|
MAX_IMAGE_MB |
|
) |
|
from prompts import DETAILED_DISCOVER_PROMPT |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
|
|
|
|
st.set_page_config( |
|
page_title="Google Discover Optimizer", |
|
page_icon="π°", |
|
layout="wide", |
|
) |
|
|
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
|
|
api_key = st.secrets.get("GOOGLE_API_KEY") or os.getenv("GOOGLE_API_KEY") |
|
|
|
|
|
gemini_model = configure_gemini(api_key) |
|
|
|
|
|
st.title("π° Google Discover Content Analyzer") |
|
st.caption("Analyze news article screenshots or text to optimize for Google Discover visibility.") |
|
|
|
|
|
input_type = st.radio( |
|
"Select Input Type:", |
|
('Screenshot Upload', 'Paste Text'), |
|
horizontal=True, |
|
key='input_type' |
|
) |
|
|
|
|
|
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, |
|
help="Maximum ~2000-2500 words (15000 characters)." |
|
) |
|
|
|
|
|
analyze_button = st.button("Analyze Content", type="primary") |
|
|
|
|
|
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 |
|
st.session_state.error_message = None |
|
st.session_state.processed_image_bytes = None |
|
image_bytes_for_analysis = None |
|
input_provided = False |
|
|
|
|
|
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") |
|
|
|
|
|
processed_image_bytes, final_format = resize_image(img_bytes) |
|
image_bytes_for_analysis = processed_image_bytes |
|
st.session_state.processed_image_bytes = processed_image_bytes |
|
|
|
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)") |
|
|
|
else: |
|
st.warning("Please provide input (upload a screenshot or paste text) before analyzing.") |
|
|
|
|
|
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) |
|
|
|
|
|
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") |
|
|
|
|
|
col1, col2 = st.columns([1, 2]) |
|
|
|
with col1: |
|
|
|
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(" ", unsafe_allow_html=True) |
|
|
|
with col2: |
|
|
|
|
|
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) |
|
|
|
st.stop() |
|
|
|
|
|
|
|
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.") |
|
|
|
|
|
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.") |
|
|
|
|
|
st.divider() |
|
try: |
|
|
|
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}") |
|
|
|
|
|
|
|
|
|
|
|
|
|
elif st.session_state.error_message: |
|
st.error(st.session_state.error_message) |
|
|
|
|
|
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="π¨") |