Spaces:
Running
Running
import streamlit as st | |
import os | |
from groq import Groq | |
from typing import List, Dict, Optional, Union | |
import json | |
from datetime import datetime | |
import time | |
from functools import lru_cache | |
import logging | |
from contextlib import contextmanager | |
# Configure logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
# Constants | |
MAX_RETRIES = 3 | |
RETRY_DELAY = 1 | |
DEFAULT_TEMPERATURE = 0.5 | |
MAX_TOKENS = 1024 | |
MODEL_NAME = "llama3-8b-8192" | |
class APIError(Exception): | |
"""Custom exception for API-related errors""" | |
pass | |
class JSONParsingError(Exception): | |
"""Custom exception for JSON parsing errors""" | |
pass | |
def error_handler(context: str): | |
"""Context manager for handling errors with specific context""" | |
try: | |
yield | |
except Exception as e: | |
logger.error(f"Error in {context}: {str(e)}") | |
st.error(f"An error occurred in {context}. Please try again.") | |
raise | |
def get_groq_client() -> Groq: | |
"""Initialize and cache Groq client""" | |
try: | |
return Groq(api_key=st.secrets["groq_api_key"]) | |
except Exception as e: | |
logger.error(f"Failed to initialize Groq client: {str(e)}") | |
st.error("Failed to initialize AI service. Please check your API key.") | |
raise APIError("Failed to initialize Groq client") | |
class ContentAnalysisAgent: | |
def __init__(self): | |
"""Initialize the agent with Groq client and default settings""" | |
self.client = get_groq_client() | |
self.system_prompt = """You are an expert social media content analyzer with deep understanding of engagement, | |
audience psychology, and content optimization. You must ALWAYS return responses in valid JSON format when requested. | |
Analyze content step by step using a systematic approach.""" | |
def _display_thinking(thought: str): | |
"""Display agent's thinking process in a collapsible container""" | |
with st.expander("π€ Analysis Process", expanded=False): | |
st.markdown(f"```\n{thought}\n```") | |
def _call_api(self, messages: List[Dict], retries: int = MAX_RETRIES) -> Optional[str]: | |
"""Make API call with retry logic""" | |
for attempt in range(retries): | |
try: | |
response = self.client.chat.completions.create( | |
messages=messages, | |
model=MODEL_NAME, | |
temperature=DEFAULT_TEMPERATURE, | |
max_tokens=MAX_TOKENS | |
) | |
return response.choices[0].message.content | |
except Exception as e: | |
if attempt == retries - 1: | |
logger.error(f"API call failed after {retries} attempts: {str(e)}") | |
raise APIError(f"Failed to get response from AI service: {str(e)}") | |
time.sleep(RETRY_DELAY) | |
return None | |
def _parse_json(response: str) -> Dict: | |
"""Parse JSON from response with enhanced error handling""" | |
try: | |
# First attempt: direct JSON parsing | |
return json.loads(response) | |
except json.JSONDecodeError: | |
try: | |
# Second attempt: extract JSON structure | |
start_idx = response.find('{') | |
end_idx = response.rfind('}') + 1 | |
if start_idx != -1 and end_idx > start_idx: | |
json_str = response[start_idx:end_idx] | |
# Clean up common formatting issues | |
json_str = (json_str.replace('\n', ' ') | |
.replace('```json', '') | |
.replace('```', '') | |
.strip()) | |
return json.loads(json_str) | |
except (json.JSONDecodeError, ValueError) as e: | |
logger.warning(f"JSON parsing failed: {str(e)}") | |
# Return fallback structure | |
return { | |
"style": "unknown", | |
"tones": ["neutral"], | |
"rating": "3", | |
"engagement_score": "50", | |
"analysis": { | |
"strengths": ["Content provided"], | |
"improvements": ["Format needs review"], | |
"audience_fit": "medium" | |
}, | |
"error": "Response parsing failed" | |
} | |
def analyze_post(self, post_text: str) -> Dict: | |
"""Analyze post content with comprehensive error handling""" | |
analysis_prompt = f"""Analyze this social media post and return ONLY a valid JSON object: | |
POST: {post_text} | |
Required structure: | |
{{ | |
"style": "posting style", | |
"tones": ["tone1", "tone2"], | |
"rating": "1-5", | |
"engagement_score": "0-100", | |
"analysis": {{ | |
"strengths": ["strength1", "strength2"], | |
"improvements": ["improvement1", "improvement2"], | |
"audience_fit": "low/medium/high" | |
}} | |
}}""" | |
messages = [ | |
{"role": "system", "content": self.system_prompt}, | |
{"role": "user", "content": analysis_prompt} | |
] | |
with st.spinner("π Analyzing content..."): | |
try: | |
analysis_response = self._call_api(messages) | |
if not analysis_response: | |
raise APIError("No response received from API") | |
analysis_result = self._parse_json(analysis_response) | |
# Get recommendations | |
recommendation_prompt = """Provide exactly 3 specific, actionable recommendations | |
to improve engagement. Return as a JSON array of strings.""" | |
messages.append({"role": "user", "content": recommendation_prompt}) | |
recommendations = self._call_api(messages) | |
if recommendations: | |
try: | |
parsed_recommendations = json.loads(recommendations) | |
if isinstance(parsed_recommendations, list): | |
analysis_result["recommendations"] = parsed_recommendations | |
else: | |
analysis_result["recommendations"] = [recommendations.strip()] | |
except json.JSONDecodeError: | |
analysis_result["recommendations"] = [recommendations.strip()] | |
return analysis_result | |
except Exception as e: | |
logger.error(f"Analysis failed: {str(e)}") | |
st.error("Analysis failed. Please try again.") | |
return None | |
class GraicieApp: | |
def __init__(self): | |
"""Initialize the Graicie application""" | |
self.agent = ContentAnalysisAgent() | |
self.example_posts = { | |
"Viral Marketing": "π HUGE ANNOUNCEMENT! After months of work, my online course is finally LIVE! π\n" | |
"Learn how I grew from 0 to 100K followers in 6 months! Early bird pricing ends tomorrow! π«\n" | |
"#socialmedia #digitalmarketing #success", | |
"Personal Story": "Sometimes life throws you curveballs... Today I faced my biggest fear and went " | |
"skydiving! πͺ Swipe to see my reaction! Remember: growth happens outside your comfort zone π\n" | |
"#personalgrowth #motivation", | |
"Educational": "π§ 5 Python Tips You Didn't Know:\n1. List comprehensions\n2. f-strings\n3. Walrus operator\n" | |
"4. Context managers\n5. Lambda functions\nSave this for later! π‘\n#coding #programming" | |
} | |
def _display_header(self): | |
"""Display application header""" | |
st.title("π€ Project Graicie - Advanced Content Analyzer") | |
st.markdown(""" | |
### Powered by LLaMA 3 & Agentic AI | |
Get deep, AI-powered insights into your social media content using advanced language models. | |
""") | |
def _display_metrics(self, results: Dict): | |
"""Display analysis metrics in a structured format""" | |
if not results: | |
return | |
# Main metrics | |
cols = st.columns(4) | |
with cols[0]: | |
st.metric("Style", results["style"]) | |
with cols[1]: | |
st.metric("Engagement", f"{results['engagement_score']}/100") | |
with cols[2]: | |
st.metric("Rating", f"{results['rating']}/5") | |
with cols[3]: | |
st.metric("Audience Fit", results["analysis"]["audience_fit"]) | |
# Content tones | |
st.subheader("π Content Tones") | |
tone_html = " ".join([ | |
f"<span style='background-color: black ; padding: 4px 8px; " | |
f"margin: 4px; border-radius: 12px;'>{tone}</span>" | |
for tone in results["tones"] | |
]) | |
st.markdown(tone_html, unsafe_allow_html=True) | |
# Analysis details | |
col1, col2 = st.columns(2) | |
with col1: | |
st.subheader("πͺ Strengths") | |
for strength in results["analysis"]["strengths"]: | |
st.markdown(f"β {strength}") | |
with col2: | |
st.subheader("π― Areas to Improve") | |
for improvement in results["analysis"]["improvements"]: | |
st.markdown(f"π {improvement}") | |
# Recommendations | |
if "recommendations" in results: | |
st.subheader("π Specific Recommendations") | |
for idx, rec in enumerate(results["recommendations"], 1): | |
st.markdown(f"{idx}. {rec}") | |
def _display_sidebar(self): | |
"""Display sidebar with tips and information""" | |
with st.sidebar: | |
st.subheader("π‘ Pro Tips") | |
st.info(""" | |
**Content Best Practices:** | |
1. Tell authentic stories | |
2. Use relevant hashtags | |
3. Include call-to-actions | |
4. Add visual elements | |
5. Engage with questions | |
""") | |
st.markdown("### π Optimal Post Elements") | |
st.markdown(""" | |
- Length: 80-150 characters | |
- Hashtags: 3-5 relevant tags | |
- Emojis: 2-3 key emojis | |
- CTA: One clear action | |
""") | |
def run(self): | |
"""Run the Graicie application""" | |
self._display_header() | |
self._display_sidebar() | |
# Main content area | |
col1, col2 = st.columns([2, 1]) | |
with col1: | |
st.subheader("π± Try an Example Post") | |
selected_example = st.selectbox( | |
"Select an example:", | |
list(self.example_posts.keys()) | |
) | |
if selected_example: | |
example_text = self.example_posts[selected_example] | |
st.text_area("Example Post", example_text, height=100, disabled=True) | |
if st.button("Analyze Example", use_container_width=True): | |
with error_handler("example analysis"): | |
results = self.agent.analyze_post(example_text) | |
self._display_metrics(results) | |
st.subheader("π Analyze Your Post") | |
user_post = st.text_area( | |
"Enter your post content:", | |
height=150, | |
placeholder="Type or paste your content here..." | |
) | |
if st.button("π Analyze My Post", use_container_width=True): | |
if user_post: | |
with error_handler("user post analysis"): | |
results = self.agent.analyze_post(user_post) | |
self._display_metrics(results) | |
else: | |
st.warning("Please enter some content to analyze!") | |
# Footer | |
st.markdown( | |
""" | |
<div style='text-align: center; padding: 20px;'> | |
<p style='color: #666;'> | |
Powered by LLaMA 3 & Groq | Made with β€οΈ by Project Graicie Team | | |
Β© 2024 Project Graicie | |
</p> | |
</div> | |
""", | |
unsafe_allow_html=True, | |
) | |
if __name__ == "__main__": | |
try: | |
app = GraicieApp() | |
app.run() | |
except Exception as e: | |
logger.error(f"Application failed to start: {str(e)}") | |
st.error("Application failed to start. Please check the logs.") |