Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| from dotenv import load_dotenv | |
| from langchain_openai import ChatOpenAI, OpenAIEmbeddings | |
| from langchain.prompts import ChatPromptTemplate | |
| from langchain_core.output_parsers import StrOutputParser | |
| from langgraph.graph import StateGraph, END | |
| from typing import Dict, TypedDict, Annotated, List, Tuple, Union, Optional | |
| import json | |
| from langchain_chroma import Chroma | |
| from langchain.schema import Document | |
| from datetime import datetime | |
| from langchain.text_splitter import RecursiveCharacterTextSplitter | |
| from pypdf import PdfReader | |
| # Load environment variables | |
| load_dotenv(verbose=True) | |
| # Verify OpenAI API key | |
| if not os.getenv("OPENAI_API_KEY"): | |
| raise ValueError("OpenAI API key not found.") | |
| # Define state types | |
| class ProcessState(TypedDict): | |
| pdf_file: str | |
| content: str | |
| enhanced: str | |
| linkedin_post: str | |
| verification: dict | |
| error: str | |
| status: str | |
| verification_score: float | |
| enhancement_attempts: int | |
| needs_improvement: bool | |
| research_context: str | |
| def extract_pdf_content(pdf_file: str) -> str: | |
| """Extract text content from PDF file.""" | |
| try: | |
| reader = PdfReader(pdf_file) | |
| text = "" | |
| for page in reader.pages: | |
| text += page.extract_text() + "\n" | |
| return text.strip() | |
| except Exception as e: | |
| raise Exception(f"Error extracting PDF content: {str(e)}") | |
| def get_content(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Get content from PDF file.""" | |
| try: | |
| progress(0.25, desc="Extracting PDF content...") | |
| content = extract_pdf_content(state["pdf_file"]) | |
| state["content"] = content | |
| state["status"] = "✅ PDF content extracted" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error extracting PDF content: {str(e)}" | |
| state["status"] = "❌ Failed to extract PDF content" | |
| return state | |
| def get_chroma_collection(): | |
| """Get or create a Chroma collection using OpenAI embeddings.""" | |
| try: | |
| collection = Chroma( | |
| collection_name="youtube_videos", | |
| embedding_function=OpenAIEmbeddings(model="text-embedding-3-small"), | |
| persist_directory="./chroma_db" | |
| ) | |
| return collection | |
| except Exception as e: | |
| raise Exception(f"Error creating Chroma collection: {str(e)}") | |
| def enhance_content(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Enhance the PDF content with semantic search and similarity analysis.""" | |
| try: | |
| if not state["content"]: | |
| return state | |
| progress(0.50, desc="Enhancing content...") | |
| # Get similar content from the vector store | |
| collection = get_chroma_collection() | |
| similar_docs = collection.similarity_search( | |
| state["content"], | |
| k=3 | |
| ) | |
| # Initialize LLM for content generation | |
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """You are an expert content enhancer. Transform this PDF content into engaging content: | |
| 1. Identify and emphasize key points | |
| 2. Add context and examples | |
| 3. Make it more engaging and professional | |
| 4. Keep it concise (max 3000 characters) | |
| 5. Maintain factual accuracy | |
| Content: | |
| {content} | |
| Similar Content for Context: | |
| {similar_content} | |
| """), | |
| ("human", "Enhance this content for a professional audience.") | |
| ]) | |
| chain = prompt | llm | StrOutputParser() | |
| state["enhanced"] = chain.invoke({ | |
| "content": state["content"], | |
| "similar_content": "\n".join([doc.page_content for doc in similar_docs]) | |
| }) | |
| state["status"] = "✅ Content enhanced" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error enhancing content: {str(e)}" | |
| state["status"] = "❌ Failed to enhance content" | |
| return state | |
| def format_linkedin_post(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Format content as a LinkedIn post.""" | |
| try: | |
| if not state["enhanced"]: | |
| return state | |
| progress(0.75, desc="Formatting for LinkedIn...") | |
| # Initialize LLM for formatting | |
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """Create an engaging LinkedIn post from this content. The post should be: | |
| 1. Natural and conversational - write like a real person sharing insights | |
| 2. Focused on value - emphasize practical takeaways and actionable insights | |
| 3. Authentic - avoid overused phrases or corporate speak | |
| 4. Visually clean - use line breaks and emojis sparingly and purposefully | |
| 5. Under 1500 characters | |
| Content Preservation Rules: | |
| - MUST maintain the exact same topic and subject matter | |
| - MUST keep all specific examples, techniques, and exercises mentioned | |
| - MUST preserve the original context and purpose | |
| - MUST include all key points from the original content | |
| - MUST maintain the same level of technical detail | |
| - MUST keep the same target audience in mind | |
| - MUST preserve any specific terminology or jargon that's important to the topic | |
| - MUST maintain the same tone and expertise level | |
| Formatting Guidelines: | |
| - Start with a hook that grabs attention | |
| - Share insights in a natural flow | |
| - Use 2-3 relevant hashtags maximum | |
| - End with a genuine call to action | |
| - Avoid numbered lists unless absolutely necessary | |
| - Don't use section headers or dividers | |
| - Don't use bullet points or emoji bullets | |
| - Don't use multiple hashtag groups | |
| Content to transform: | |
| {content} | |
| Remember: The goal is to make the content more engaging while keeping ALL the original information, examples, and technical details intact."""), | |
| ("human", "Create a natural, engaging LinkedIn post that preserves all the original content and context.") | |
| ]) | |
| chain = prompt | llm | StrOutputParser() | |
| state["linkedin_post"] = chain.invoke({"content": state["enhanced"]}) | |
| state["status"] = "✅ LinkedIn post formatted" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error formatting LinkedIn post: {str(e)}" | |
| state["status"] = "❌ Failed to format LinkedIn post" | |
| return state | |
| def verify_content(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Verify the enhanced content against the original using semantic similarity.""" | |
| try: | |
| if not state["enhanced"] or not state["content"]: | |
| return state | |
| progress(1.0, desc="Verifying content...") | |
| # Initialize enhancement attempts if not present | |
| if "enhancement_attempts" not in state: | |
| state["enhancement_attempts"] = 0 | |
| # Calculate semantic similarity using Chroma | |
| collection = get_chroma_collection() | |
| similar_docs = collection.similarity_search( | |
| state["enhanced"], | |
| k=1 | |
| ) | |
| similarity_score = 0.0 | |
| if similar_docs: | |
| # Chroma returns a list of Document objects with a score attribute | |
| # But the default similarity_search does not return scores, so we just check if content is similar | |
| similarity_score = 1.0 if similar_docs[0].page_content == state["content"] else 0.0 | |
| # Initialize LLM for verification | |
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """Verify the enhanced content against the original: | |
| 1. Check factual accuracy | |
| 2. Ensure key messages are preserved | |
| 3. Look for any misrepresentations | |
| Return JSON in this format: | |
| {{ | |
| "verified": boolean, | |
| "score": float between 0-1, | |
| "feedback": string with details | |
| }} | |
| Original: | |
| {original} | |
| Enhanced: | |
| {enhanced} | |
| Semantic Similarity Score: {similarity_score}"""), | |
| ("human", "Verify this content.") | |
| ]) | |
| chain = prompt | llm | StrOutputParser() | |
| verification_result = json.loads(chain.invoke({ | |
| "original": state["content"], | |
| "enhanced": state["enhanced"], | |
| "similarity_score": similarity_score | |
| })) | |
| # Update state with verification results | |
| state["verification"] = verification_result | |
| state["verification_score"] = verification_result["score"] | |
| # Trigger agent decision if score is below threshold | |
| if verification_result["score"] < 0.85 and state["enhancement_attempts"] < 3: | |
| state["needs_improvement"] = True | |
| # Create improvement plan | |
| state = agent_decide(state) | |
| state["status"] = f"🔄 Planning improvements (Attempt {state['enhancement_attempts'] + 1}/3)" | |
| else: | |
| state["needs_improvement"] = False | |
| if verification_result["score"] >= 0.85: | |
| state["status"] = "✅ Content quality threshold met" | |
| else: | |
| state["status"] = "⚠️ Max enhancement attempts reached" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error verifying content: {str(e)}" | |
| state["status"] = "❌ Failed to verify content" | |
| return state | |
| def should_continue(state: ProcessState) -> bool: | |
| """Determine if processing should continue.""" | |
| return not state.get("error", "") | |
| def create_workflow() -> StateGraph: | |
| """Create the LangGraph workflow.""" | |
| workflow = StateGraph(ProcessState) | |
| # Add nodes | |
| workflow.add_node("get_content", get_content) | |
| workflow.add_node("enhance_content", enhance_content) | |
| workflow.add_node("format_linkedin", format_linkedin_post) | |
| workflow.add_node("verify_content", verify_content) | |
| workflow.add_node("agent_decide", agent_decide) | |
| workflow.add_node("research_content", research_content) | |
| workflow.add_node("enhance_again", enhance_again) | |
| # Set entry point | |
| workflow.set_entry_point("get_content") | |
| # Add edges for main flow | |
| workflow.add_edge("get_content", "enhance_content") | |
| workflow.add_edge("enhance_content", "format_linkedin") | |
| workflow.add_edge("format_linkedin", "verify_content") | |
| workflow.add_edge("verify_content", "agent_decide") | |
| # Add conditional edges for agentic flow | |
| workflow.add_conditional_edges( | |
| "agent_decide", | |
| lambda x: x["needs_improvement"], | |
| { | |
| True: "research_content", | |
| False: END | |
| } | |
| ) | |
| # Add edges for enhancement loop | |
| workflow.add_edge("research_content", "enhance_again") | |
| workflow.add_edge("enhance_again", "verify_content") | |
| # Add conditional edges for error handling | |
| workflow.add_conditional_edges( | |
| "get_content", | |
| should_continue, | |
| { | |
| True: "enhance_content", | |
| False: END | |
| } | |
| ) | |
| workflow.add_conditional_edges( | |
| "enhance_content", | |
| should_continue, | |
| { | |
| True: "format_linkedin", | |
| False: END | |
| } | |
| ) | |
| workflow.add_conditional_edges( | |
| "format_linkedin", | |
| should_continue, | |
| { | |
| True: "verify_content", | |
| False: END | |
| } | |
| ) | |
| workflow.add_conditional_edges( | |
| "verify_content", | |
| should_continue, | |
| { | |
| True: "agent_decide", | |
| False: END | |
| } | |
| ) | |
| workflow.add_conditional_edges( | |
| "research_content", | |
| should_continue, | |
| { | |
| True: "enhance_again", | |
| False: END | |
| } | |
| ) | |
| workflow.add_conditional_edges( | |
| "enhance_again", | |
| should_continue, | |
| { | |
| True: "verify_content", | |
| False: END | |
| } | |
| ) | |
| return workflow | |
| def process_video(video_url: str, progress=gr.Progress()) -> tuple: | |
| """Process YouTube video and generate LinkedIn post.""" | |
| try: | |
| # Input validation | |
| if not video_url: | |
| return ( | |
| "⚠️ Please enter a YouTube URL", # error | |
| "❌ Failed: No URL provided", # status | |
| "", # transcript | |
| "", # enhanced | |
| "" # verification | |
| ) | |
| if "youtube.com" not in video_url and "youtu.be" not in video_url: | |
| return ( | |
| "⚠️ Invalid URL. Please enter a YouTube URL", # error | |
| "❌ Failed: Invalid URL", # status | |
| "", # transcript | |
| "", # enhanced | |
| "" # verification | |
| ) | |
| # Initialize state | |
| initial_state = ProcessState( | |
| video_url=video_url, | |
| transcript="", | |
| enhanced="", | |
| linkedin_post="", | |
| verification={}, | |
| error="", | |
| status="Starting..." | |
| ) | |
| # Create and run workflow | |
| workflow = create_workflow() | |
| app = workflow.compile() | |
| final_state = app.invoke(initial_state) | |
| # Format verification text | |
| if final_state.get("verification"): | |
| verification_text = f"""Verification Results: | |
| • Status: {"✅ Verified" if final_state["verification"]["verified"] else "❌ Not Verified"} | |
| • Accuracy Score: {final_state["verification"]["score"]:.2f} | |
| • Feedback: {final_state["verification"]["feedback"]}""" | |
| else: | |
| verification_text = "" | |
| return ( | |
| final_state.get("error", ""), # error | |
| final_state.get("status", ""), # status | |
| final_state.get("transcript", ""), # transcript | |
| final_state.get("enhanced", ""), # enhanced | |
| final_state.get("linkedin_post", ""), # linkedin | |
| verification_text # verification | |
| ) | |
| except Exception as e: | |
| return ( | |
| f"⚠️ Error: {str(e)}", # error | |
| "❌ Processing failed", # status | |
| "", # transcript | |
| "", # enhanced | |
| "" # verification | |
| ) | |
| def process_from_stage(state: ProcessState, start_stage: str, progress=gr.Progress()) -> tuple: | |
| """Process content from a specific stage onwards.""" | |
| try: | |
| # Select appropriate workflow based on stage | |
| if start_stage == "enhance": | |
| workflow = create_workflow() | |
| if not state["content"]: | |
| return ( | |
| "⚠️ No content available to enhance", | |
| "❌ Failed: No content", | |
| state.get("content", ""), | |
| "", | |
| "", | |
| "" | |
| ) | |
| elif start_stage == "format": | |
| workflow = create_workflow() | |
| if not state["enhanced"]: | |
| return ( | |
| "⚠️ No enhanced content available to format", | |
| "❌ Failed: No enhanced content", | |
| state.get("content", ""), | |
| state.get("enhanced", ""), | |
| "", | |
| "" | |
| ) | |
| else: | |
| workflow = create_workflow() | |
| app = workflow.compile() | |
| final_state = app.invoke(state) | |
| # Format verification text | |
| if final_state.get("verification"): | |
| verification_text = f"""Verification Results: | |
| • Status: {"✅ Verified" if final_state["verification"]["verified"] else "❌ Not Verified"} | |
| • Accuracy Score: {final_state["verification"]["score"]:.2f} | |
| • Feedback: {final_state["verification"]["feedback"]}""" | |
| else: | |
| verification_text = "" | |
| return ( | |
| final_state.get("error", ""), | |
| final_state.get("status", ""), | |
| final_state.get("content", ""), | |
| final_state.get("enhanced", ""), | |
| final_state.get("linkedin_post", ""), | |
| verification_text | |
| ) | |
| except Exception as e: | |
| return ( | |
| f"⚠️ Error: {str(e)}", | |
| "❌ Processing failed", | |
| state.get("content", ""), | |
| state.get("enhanced", ""), | |
| state.get("linkedin_post", ""), | |
| "" | |
| ) | |
| def format_verification_text(verification: dict) -> str: | |
| """Format verification results into a readable string.""" | |
| if not verification: | |
| return "" | |
| return f"""Verification Results: | |
| • Status: {"✅ Verified" if verification.get("verified") else "❌ Not Verified"} | |
| • Accuracy Score: {verification.get("score", 0):.2f} | |
| • Feedback: {verification.get("feedback", "No feedback available")}""" | |
| def safe_json_loads(json_str: str, default: dict = None) -> dict: | |
| """Safely parse JSON string with error handling.""" | |
| if default is None: | |
| default = {} | |
| try: | |
| return json.loads(json_str) if json_str else default | |
| except json.JSONDecodeError: | |
| return default | |
| def format_improvement_plan(plan: dict) -> str: | |
| """Format the improvement plan into a readable string.""" | |
| if not plan: | |
| return "No improvement plan available" | |
| text = "📋 Improvement Plan:\n\n" | |
| # Improvement Areas | |
| if "improvement_areas" in plan: | |
| text += "🎯 Priority Areas:\n" | |
| for area in plan["improvement_areas"]: | |
| text += f"• {area.get('area', 'N/A')} (Priority: {area.get('priority', 'N/A')}/5)\n" | |
| text += f" Strategy: {area.get('strategy', 'N/A')}\n" | |
| text += f" Research Focus: {area.get('research_focus', 'N/A')}\n\n" | |
| # Research Priorities | |
| if "research_priorities" in plan: | |
| text += "🔍 Research Priorities:\n" | |
| for topic in plan["research_priorities"]: | |
| text += f"• {topic.get('topic', 'N/A')}\n" | |
| text += f" Reason: {topic.get('reason', 'N/A')}\n" | |
| text += f" Expected Impact: {topic.get('expected_impact', 'N/A')}\n\n" | |
| # Enhancement Strategy | |
| if "enhancement_strategy" in plan: | |
| text += "⚡ Enhancement Strategy:\n" | |
| strategy = plan["enhancement_strategy"] | |
| text += f"• Approach: {strategy.get('approach', 'N/A')}\n" | |
| text += f"• Key Focus: {strategy.get('key_focus', 'N/A')}\n" | |
| text += "• Expected Improvements:\n" | |
| for imp in strategy.get("expected_improvements", []): | |
| text += f" - {imp}\n" | |
| return text | |
| def format_research_results(research: dict) -> str: | |
| """Format the research results into a readable string.""" | |
| if not research: | |
| return "No research results available" | |
| text = "📚 Research Results:\n\n" | |
| # Focused Research | |
| if "focused_research" in research: | |
| text += "🎯 Focused Research by Area:\n" | |
| for area, data in research["focused_research"].items(): | |
| text += f"• {area} (Priority: {data.get('priority', 'N/A')}/5)\n" | |
| text += f" Strategy: {data.get('strategy', 'N/A')}\n" | |
| text += " Key Findings:\n" | |
| for content in data.get("content", [])[:1]: # Show first finding | |
| text += f" - {content[:200]}...\n\n" | |
| # Additional Research | |
| if research.get("similar_content"): | |
| text += "📖 Additional Research:\n" | |
| for content in research["similar_content"][:2]: # Show first two | |
| text += f"• {content[:200]}...\n\n" | |
| return text | |
| def create_ui(): | |
| with gr.Blocks(theme='JohnSmith9982/small_and_pretty') as demo: | |
| current_state = gr.State({ | |
| "pdf_file": "", | |
| "content": "", | |
| "enhanced": "", | |
| "linkedin_post": "", | |
| "verification": {}, | |
| "error": "", | |
| "status": "", | |
| "improvement_plan": {}, | |
| "research_context": "{}", | |
| "enhancement_attempts": 0, | |
| "needs_improvement": False | |
| }) | |
| gr.Markdown( | |
| """ | |
| # PDF to LinkedIn Post Converter | |
| Transform your PDF documents into professional LinkedIn posts with AI content enhancement. | |
| ### 📄 How to Use | |
| 1. Upload a PDF file | |
| 2. Click "Generate Post" | |
| 3. Review the enhanced content | |
| 4. Copy your LinkedIn-ready post | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| pdf_file = gr.File( | |
| label="PDF File", | |
| file_types=[".pdf"], | |
| type="filepath" | |
| ) | |
| convert_btn = gr.Button("🚀 Generate from PDF", variant="primary", size="lg") | |
| status = gr.Textbox( | |
| label="Status", | |
| value="Ready to process...", | |
| interactive=False | |
| ) | |
| error = gr.Textbox( | |
| label="Error", | |
| visible=False, | |
| interactive=False | |
| ) | |
| with gr.Tabs() as tabs: | |
| with gr.TabItem("📝 Content"): | |
| with gr.Row(): | |
| with gr.Column(): | |
| content = gr.TextArea( | |
| label="📄 Raw Content", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=8 | |
| ) | |
| with gr.Column(): | |
| enhanced = gr.TextArea( | |
| label="✨ Enhanced Content", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=8 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| linkedin = gr.TextArea( | |
| label="🔗 LinkedIn Post", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=6 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| verification = gr.TextArea( | |
| label="✓ Verification Results", | |
| interactive=False, | |
| lines=4 | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| improvement_plan = gr.TextArea( | |
| label="📋 Improvement Plan", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=8, | |
| visible=True, | |
| value="Waiting for verification..." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| research_results = gr.TextArea( | |
| label="🔍 Research Results", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=8, | |
| visible=True, | |
| value="Waiting for research..." | |
| ) | |
| with gr.Row(): | |
| with gr.Column(): | |
| improved_linkedin = gr.TextArea( | |
| label="🚀 Improved LinkedIn Post Final", | |
| interactive=False, | |
| show_copy_button=True, | |
| lines=6, | |
| visible=True, | |
| value="Waiting for improvements..." | |
| ) | |
| # Loading indicators | |
| with gr.Row(visible=False) as loading_indicators: | |
| content_loading = gr.Markdown("🔄 Extracting content...") | |
| enhanced_loading = gr.Markdown("🔄 Enhancing content...") | |
| linkedin_loading = gr.Markdown("🔄 Formatting for LinkedIn...") | |
| verify_loading = gr.Markdown("🔄 Verifying content...") | |
| plan_loading = gr.Markdown("🔄 Creating improvement plan...") | |
| research_loading = gr.Markdown("🔄 Researching content...") | |
| improved_loading = gr.Markdown("🔄 Creating improved post...") | |
| with gr.TabItem("ℹ️ Help"): | |
| gr.Markdown( | |
| """ | |
| ### How to Use | |
| 1. **Input**: Upload a PDF file | |
| 2. **Process**: Click the "Generate Post" button | |
| 3. **Wait**: The system will process your PDF through multiple steps | |
| 4. **Review**: Check the generated content in each tab | |
| 5. **Copy**: Use the copy button to grab your LinkedIn post | |
| ### 🔄 Regeneration Options | |
| - Click 🔄 next to "Enhanced Content" to regenerate from the enhancement stage | |
| - Click 🔄 next to "LinkedIn Post" to regenerate from the formatting stage | |
| ### 💡 Tips for Best Results | |
| - Use well-formatted PDFs with clear text | |
| - Optimal length: 2-10 pages | |
| - Ensure PDFs have readable text (not scanned images) | |
| - Review and personalize the post before sharing | |
| - Consider your target audience when selecting content | |
| """ | |
| ) | |
| def update_loading_state(stage: str): | |
| """Update loading indicators based on current stage.""" | |
| states = { | |
| "content": [True, False, False, False, False, False, False], | |
| "enhance": [False, True, False, False, False, False, False], | |
| "format": [False, False, True, False, False, False, False], | |
| "verify": [False, False, False, True, False, False, False], | |
| "plan": [False, False, False, False, True, False, False], | |
| "research": [False, False, False, False, False, True, False], | |
| "improved": [False, False, False, False, False, False, True], | |
| "done": [False, False, False, False, False, False, False] | |
| } | |
| # Loading messages for each stage | |
| loading_messages = { | |
| "content": "🔄 Extracting content...\n⏳ Please wait...", | |
| "enhance": "✨ Enhancing content...\n⚡ AI is working its magic...", | |
| "format": "🎨 Formatting for LinkedIn...\n📝 Creating engaging post...", | |
| "verify": "🔍 Verifying content...\n⚖️ Checking accuracy...", | |
| "plan": "🔄 Creating improvement plan...", | |
| "research": "🔎 Researching content...\n📚 Finding relevant information...", | |
| "improved": "🚀 Creating improved LinkedIn post...\n✨ Applying enhancements..." | |
| } | |
| # Get current stage message | |
| current_message = loading_messages.get(stage, "") | |
| # Return loading states and message | |
| return [ | |
| gr.update(visible=state) for state in states.get(stage, [False] * 7) | |
| ], current_message | |
| def process_with_loading(pdf_path, state): | |
| """Process PDF with loading indicators.""" | |
| try: | |
| # Initialize state if needed | |
| if "improvement_plan" not in state: | |
| state["improvement_plan"] = {} | |
| if "research_context" not in state: | |
| state["research_context"] = "{}" | |
| if "enhancement_attempts" not in state: | |
| state["enhancement_attempts"] = 0 | |
| if "needs_improvement" not in state: | |
| state["needs_improvement"] = False | |
| # Show loading indicators | |
| loading_states, message = update_loading_state("content") | |
| yield [ | |
| "", # error | |
| "Processing...", # status | |
| message, # content (loading) | |
| "", # enhanced | |
| "", # verification | |
| "Waiting for verification...", # improvement plan | |
| "Waiting for research...", # research results | |
| "Waiting for improvements...", # improved linkedin | |
| state, # current_state | |
| *loading_states # loading indicators | |
| ] | |
| # Get content | |
| state["pdf_file"] = pdf_path | |
| content_text = get_content(state)["content"] | |
| # Show enhancing state | |
| loading_states, message = update_loading_state("enhance") | |
| yield [ | |
| "", | |
| "Enhancing content...", | |
| content_text, | |
| message, # enhanced (loading) | |
| "", | |
| "", | |
| "", | |
| "", | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Enhance content | |
| state["content"] = content_text | |
| enhanced_state = enhance_content(state) | |
| enhanced_text = enhanced_state["enhanced"] | |
| # Show formatting state | |
| loading_states, message = update_loading_state("format") | |
| yield [ | |
| "", | |
| "Formatting for LinkedIn...", | |
| content_text, | |
| enhanced_text, | |
| message, # linkedin (loading) | |
| "", | |
| "", | |
| "", | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Format LinkedIn post | |
| state["enhanced"] = enhanced_text | |
| linkedin_state = format_linkedin_post(state) | |
| linkedin_text = linkedin_state["linkedin_post"] | |
| # Show verifying state | |
| loading_states, message = update_loading_state("verify") | |
| yield [ | |
| "", | |
| "Verifying content...", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| "🔍 Verifying...\n⚖️ Analyzing accuracy...", # verification (loading) | |
| "", | |
| "", | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Verify content | |
| state["linkedin_post"] = linkedin_text | |
| final_state = verify_content(state) | |
| verification_text = format_verification_text(final_state.get("verification", {})) | |
| # Update improvement plan and research results | |
| improvement_plan_text = format_improvement_plan(final_state.get("improvement_plan", {})) | |
| research_results_text = format_research_results(safe_json_loads(final_state.get("research_context", "{}"))) | |
| # Check if enhancement is needed | |
| if final_state.get("needs_improvement", False): | |
| # Show planning state | |
| loading_states, message = update_loading_state("plan") | |
| yield [ | |
| "", | |
| f"Creating improvement plan (Attempt {final_state.get('enhancement_attempts', 1)}/3)...", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| verification_text, | |
| improvement_plan_text, | |
| research_results_text, | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Show researching state | |
| loading_states, message = update_loading_state("research") | |
| yield [ | |
| "", | |
| f"Researching content (Attempt {final_state.get('enhancement_attempts', 1)}/3)...", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| verification_text, | |
| improvement_plan_text, | |
| research_results_text, | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Research content | |
| state = research_content(state) | |
| research_results_text = format_research_results(safe_json_loads(state.get("research_context", "{}"))) | |
| # Show enhancing again state | |
| loading_states, message = update_loading_state("enhance") | |
| yield [ | |
| "", | |
| f"Enhancing content again (Attempt {final_state.get('enhancement_attempts', 1)}/3)...", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| verification_text, | |
| improvement_plan_text, | |
| research_results_text, | |
| "", | |
| state, | |
| *loading_states | |
| ] | |
| # Enhance again | |
| state = enhance_again(state) | |
| enhanced_text = state["enhanced"] | |
| # Update LinkedIn post | |
| state["enhanced"] = enhanced_text | |
| linkedin_state = format_linkedin_post(state) | |
| linkedin_text = linkedin_state["linkedin_post"] | |
| # Verify again | |
| state["linkedin_post"] = linkedin_text | |
| final_state = verify_content(state) | |
| verification_text = format_verification_text(final_state.get("verification", {})) | |
| improvement_plan_text = format_improvement_plan(final_state.get("improvement_plan", {})) | |
| research_results_text = format_research_results(safe_json_loads(final_state.get("research_context", "{}"))) | |
| # After research and enhancement, create improved LinkedIn post | |
| if final_state.get("needs_improvement", False): | |
| # Show improved post loading state | |
| loading_states, message = update_loading_state("improved") | |
| yield [ | |
| "", | |
| f"Creating improved LinkedIn post (Attempt {final_state.get('enhancement_attempts', 1)}/3)...", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| verification_text, | |
| improvement_plan_text, | |
| research_results_text, | |
| message, # improved linkedin (loading) | |
| state, | |
| *loading_states | |
| ] | |
| # Create improved LinkedIn post | |
| improved_state = format_linkedin_post(final_state) | |
| improved_text = improved_state["linkedin_post"] | |
| # Update final state | |
| final_state["improved_linkedin"] = improved_text | |
| # Complete | |
| loading_states, _ = update_loading_state("done") | |
| yield [ | |
| "", | |
| "✅ Processing complete!", | |
| content_text, | |
| enhanced_text, | |
| linkedin_text, | |
| verification_text, | |
| improvement_plan_text, | |
| research_results_text, | |
| final_state.get("improved_linkedin", "No improvements needed"), | |
| final_state, | |
| *loading_states | |
| ] | |
| except Exception as e: | |
| loading_states, _ = update_loading_state("done") | |
| yield [ | |
| f"⚠️ Error: {str(e)}", | |
| "❌ Processing failed", | |
| state.get("content", ""), | |
| state.get("enhanced", ""), | |
| state.get("linkedin_post", ""), | |
| "", | |
| "Error occurred during processing", | |
| "Error occurred during processing", | |
| "Error occurred during processing", | |
| state, | |
| *loading_states | |
| ] | |
| # Set up event handlers | |
| convert_btn.click( | |
| fn=process_with_loading, | |
| inputs=[pdf_file, current_state], | |
| outputs=[ | |
| error, | |
| status, | |
| content, | |
| enhanced, | |
| linkedin, | |
| verification, | |
| improvement_plan, | |
| research_results, | |
| improved_linkedin, | |
| current_state, | |
| content_loading, | |
| enhanced_loading, | |
| linkedin_loading, | |
| verify_loading, | |
| plan_loading, | |
| research_loading, | |
| improved_loading | |
| ], | |
| show_progress=True, # Show progress bar | |
| api_name="convert" # Name the API endpoint | |
| ) | |
| # Update error visibility with immediate feedback | |
| error.change( | |
| lambda x: gr.update(visible=bool(x), value=x), # Update both visibility and value | |
| error, | |
| error, | |
| queue=False # Process immediately | |
| ) | |
| # Add loading state visibility updates | |
| def update_loading_visibility(is_loading): | |
| return { | |
| loading: gr.update(visible=is_loading) | |
| for loading in [ | |
| content_loading, | |
| enhanced_loading, | |
| linkedin_loading, | |
| verify_loading, | |
| plan_loading, | |
| research_loading, | |
| improved_loading | |
| ] | |
| } | |
| convert_btn.click( | |
| lambda: update_loading_visibility(True), | |
| None, | |
| [content_loading, enhanced_loading, linkedin_loading, | |
| verify_loading, plan_loading, research_loading, improved_loading], | |
| queue=False | |
| ) | |
| return demo | |
| def agent_decide(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Agent decides whether to enhance content further based on verification score and creates an improvement plan.""" | |
| try: | |
| progress(0.95, desc="Analyzing content quality and planning improvements...") | |
| # Get verification score and attempts | |
| score = state.get("verification", {}).get("score", 0) | |
| attempts = state.get("enhancement_attempts", 0) | |
| feedback = state.get("verification", {}).get("feedback", "") | |
| # Initialize LLM for agentic decision making | |
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """You are an expert content strategist. Analyze the content quality and create an improvement plan. | |
| Current Content: | |
| {content} | |
| Verification Results: | |
| - Score: {score} | |
| - Feedback: {feedback} | |
| - Previous Attempts: {attempts} | |
| Create a detailed improvement plan in JSON format: | |
| {{ | |
| "needs_improvement": boolean, | |
| "improvement_areas": [ | |
| {{ | |
| "area": string, | |
| "priority": number (1-5), | |
| "strategy": string, | |
| "research_focus": string | |
| }} | |
| ], | |
| "research_priorities": [ | |
| {{ | |
| "topic": string, | |
| "reason": string, | |
| "expected_impact": string | |
| }} | |
| ], | |
| "enhancement_strategy": {{ | |
| "approach": string, | |
| "key_focus": string, | |
| "expected_improvements": [string] | |
| }} | |
| }} | |
| Consider: | |
| 1. Content quality and engagement | |
| 2. Information accuracy and completeness | |
| 3. Target audience needs | |
| 4. Previous enhancement attempts | |
| 5. Available research context"""), | |
| ("human", "Analyze this content and create an improvement plan.") | |
| ]) | |
| chain = prompt | llm | StrOutputParser() | |
| plan = json.loads(chain.invoke({ | |
| "content": state["enhanced"], | |
| "score": score, | |
| "feedback": feedback, | |
| "attempts": attempts | |
| })) | |
| # Update state with plan | |
| state["verification_score"] = score | |
| state["enhancement_attempts"] = attempts | |
| state["needs_improvement"] = plan["needs_improvement"] | |
| state["improvement_plan"] = plan | |
| # Create detailed status message | |
| if plan["needs_improvement"] and attempts < 3: | |
| status = f"🔄 Planning improvements (Attempt {attempts + 1}/3)\n" | |
| status += "Key focus areas:\n" | |
| for area in plan["improvement_areas"][:2]: # Show top 2 priorities | |
| status += f"• {area['area']} (Priority: {area['priority']})\n" | |
| state["status"] = status | |
| else: | |
| if score >= 0.95: | |
| state["status"] = "✅ Content quality threshold met" | |
| else: | |
| state["status"] = "⚠️ Max enhancement attempts reached" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error in agent decision: {str(e)}" | |
| state["status"] = "❌ Failed to analyze content" | |
| return state | |
| def research_content(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Research additional context based on the improvement plan.""" | |
| try: | |
| progress(0.96, desc="Researching based on improvement plan...") | |
| # Get improvement plan | |
| plan = state.get("improvement_plan", {}) | |
| if not plan: | |
| raise Exception("No improvement plan found") | |
| # Initialize research results | |
| research_results = { | |
| "similar_content": [], | |
| "focused_research": {}, | |
| "verification_feedback": state.get("verification", {}).get("feedback", "") | |
| } | |
| # Get similar content from vector store | |
| collection = get_chroma_collection() | |
| # Research each priority area | |
| for area in plan["improvement_areas"]: | |
| # Search for content related to this area | |
| similar_docs = collection.similarity_search( | |
| f"{area['area']} {area['research_focus']}", | |
| k=2 | |
| ) | |
| # Store research results | |
| research_results["focused_research"][area["area"]] = { | |
| "content": [doc.page_content for doc in similar_docs], | |
| "priority": area["priority"], | |
| "strategy": area["strategy"] | |
| } | |
| # Research specific topics from research_priorities | |
| for topic in plan["research_priorities"]: | |
| topic_docs = collection.similarity_search( | |
| topic["topic"], | |
| k=1 | |
| ) | |
| if topic_docs: | |
| research_results["similar_content"].extend([doc.page_content for doc in topic_docs]) | |
| # Store research results | |
| state["research_context"] = json.dumps(research_results) | |
| state["status"] = "✅ Research completed based on improvement plan" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error researching content: {str(e)}" | |
| state["status"] = "❌ Failed to research content" | |
| return state | |
| def enhance_again(state: ProcessState, progress=gr.Progress()) -> ProcessState: | |
| """Enhance content using research and improvement plan.""" | |
| try: | |
| progress(0.97, desc="Enhancing content based on research and plan...") | |
| # Get research context and improvement plan | |
| research_context = json.loads(state["research_context"]) | |
| plan = state.get("improvement_plan", {}) | |
| if not plan: | |
| raise Exception("No improvement plan found") | |
| # Initialize LLM for enhancement | |
| llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7) | |
| prompt = ChatPromptTemplate.from_messages([ | |
| ("system", """You are an expert content enhancer. Improve the content based on the research and improvement plan while maintaining the original topic and key messages. | |
| Current Content: | |
| {content} | |
| Improvement Plan: | |
| {plan} | |
| Research Results: | |
| {research} | |
| Enhancement Strategy: | |
| {strategy} | |
| Create enhanced content that: | |
| 1. Maintains the original topic and key messages | |
| 2. Addresses each improvement area according to its priority | |
| 3. Incorporates relevant research findings | |
| 4. Follows the enhancement strategy | |
| 5. Improves engagement and clarity | |
| 6. Keeps the same core subject matter and examples | |
| Important: | |
| - DO NOT change the main topic or subject matter | |
| - DO NOT replace specific examples with generic ones | |
| - DO NOT lose the original context or purpose | |
| - DO NOT generate content about a different topic | |
| - DO preserve and enhance the original message"""), | |
| ("human", "Enhance this content while maintaining its original topic and key messages.") | |
| ]) | |
| chain = prompt | llm | StrOutputParser() | |
| enhanced = chain.invoke({ | |
| "content": state["enhanced"], | |
| "plan": json.dumps(plan), | |
| "research": json.dumps(research_context), | |
| "strategy": json.dumps(plan["enhancement_strategy"]) | |
| }) | |
| # Update state | |
| state["enhanced"] = enhanced | |
| state["enhancement_attempts"] = state.get("enhancement_attempts", 0) + 1 | |
| state["status"] = f"✅ Content enhanced with research (Attempt {state['enhancement_attempts']}/3)" | |
| return state | |
| except Exception as e: | |
| state["error"] = f"⚠️ Error enhancing content: {str(e)}" | |
| state["status"] = "❌ Failed to enhance content" | |
| return state | |
| if __name__ == "__main__": | |
| demo = create_ui() | |
| demo.launch(server_name="0.0.0.0", server_port=7860, share=False) |