CoolFace
Apppublic

NifemiAluko/linkedin-post-analyzer

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py784 linesDownload Raw Back to root
1import gradio as gr2import pandas as pd3from helpers import (4    process_dataframe,5    generate_summary,6    setup_langchain,7    batch_analyze_posts,8    word_count,9    calculate_reading_ease,10    setup_safe_analysis_agent,11    determine_media_type12)13import json14from langchain.text_splitter import RecursiveCharacterTextSplitter15from langchain.prompts import PromptTemplate16from langchain.chains.llm import LLMChain17from langchain_community.llms import OpenAI18import os19from langchain_openai import ChatOpenAI20import numpy as np21from langchain.schema import HumanMessage22from dotenv import load_dotenv23 24# Add these imports to the top of your main file25from langchain_experimental.agents import create_pandas_dataframe_agent26from langchain_openai import ChatOpenAI27from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder28from langchain.memory import ConversationBufferMemory29from langchain_core.messages import AIMessage, HumanMessage30import textwrap 31from langchain.tools import tool32from langchain_core.prompts import ChatPromptTemplate33from langchain.agents import AgentExecutor, create_openai_tools_agent34from langchain_core.tools import Tool35 36# Make sure environment variables are loaded37load_dotenv()38 39# Define the data loading function FIRST, before it's used40def preprocess_data(df):41    """42    Preprocess the uploaded dataframe43    44    Args:45        df: pandas DataFrame46    47    Returns:48        pandas DataFrame: Preprocessed LinkedIn post data49    """50    # Convert engagement_rate to numeric if it exists51    if 'Engagement_rate' in df.columns:52        df['Engagement_rate'] = pd.to_numeric(df['Engagement_rate'], errors='coerce')53    elif 'Engagement Rate' in df.columns:54        df['Engagement_rate'] = pd.to_numeric(df['Engagement Rate'], errors='coerce')55        df = df.rename(columns={'Engagement Rate': 'Engagement_rate'})56    57    # Handle numeric columns58    numeric_columns = ['Reactions', 'Comment', 'Reposts', 'Word Count', 'Flesch Reading Ease']59    for col in numeric_columns:60        if col in df.columns:61            df[col] = pd.to_numeric(df[col], errors='coerce')62    63    # Fill NaN values with appropriate defaults64    df = df.fillna({65        'Reactions': 0,66        'Comment': 0,67        'Reposts': 0,68        'Engagement_rate': 069    })70    71    # Print column information for debugging72    print(f"Available columns: {df.columns.tolist()}")73    74    return df75 76def analyze_file(file):77    try:78        if file.name.endswith('.csv'):79            try:80                df = pd.read_csv(file.name, encoding='latin-1')81            except Exception as e:82                df = pd.read_csv(file.name, encoding='iso-8859-1')83        elif file.name.endswith('.xlsx') or file.name.endswith('.xls'):84            df = pd.read_excel(file.name)85        else:86            return "Unsupported file format. Please upload a CSV or Excel file.", None, None, None, None87 88        # Debug information89        original_row_count = len(df)90        print(f"Original file has {original_row_count} rows")91        92        # Process DataFrame using batch processing93        analysis_results = batch_analyze_posts(df['Full Post'].tolist())94        95        # Create a DataFrame from analysis results96        results_df = pd.DataFrame(analysis_results)97        98        # Combine with original DataFrame99        processed_df = pd.concat([df, results_df], axis=1)100        101        # Add Word Count and Reading Ease calculations102        processed_df['Word Count'] = processed_df['Full Post'].apply(word_count)103        processed_df['Flesch Reading Ease'] = processed_df['Full Post'].apply(calculate_reading_ease)104        105        # Add Media Creative analysis106        if 'Media' in processed_df.columns:107            processed_df['Media Creative'] = processed_df['Media'].apply(determine_media_type)108            print("Added Media Creative column")  # Debug print109        110        # Convert Engagement_rate to numeric if it exists111        if 'Engagement_rate' in processed_df.columns:112            try:113                processed_df['Engagement_rate'] = pd.to_numeric(processed_df['Engagement_rate'].astype(str).str.replace(',', ''), errors='coerce').fillna(0)114                print("Found and processed Engagement_rate column")115            except Exception as e:116                print(f"Warning: Could not process Engagement_rate column: {str(e)}")117 118        # Generate Markdown Summary119        markdown_summary = generate_summary(processed_df)120 121        # Save the updated Excel file122        updated_file = "updated_li_posts.xlsx"123        processed_df.to_excel(updated_file, index=False)124 125        # Save the markdown summary126        summary_file = "summary.md"127        with open(summary_file, 'w') as f:128            f.write(markdown_summary)129 130        return (131            f"Analysis complete! Processed {len(processed_df)} out of {original_row_count} posts.", 132            updated_file, 133            summary_file,134            processed_df,135            markdown_summary136        )137 138    except Exception as e:139        return f"An error occurred: {str(e)}", None, None, None, None140 141def chat_with_data(question, df, chat_history=None, summary=None):142    try:143        if df is None:144            return [145                {"role": "user", "content": question},146                {"role": "assistant", "content": "Please analyze a file first."}147            ]148            149        # Create a copy to work with150        df = df.copy()151        152        # Ensure key columns are numeric153        for col in ['Reactions', 'Comment', 'Reposts', 'Word Count', 'Flesch Reading Ease', 'Engagement_rate']:154            if col in df.columns:155                df[col] = pd.to_numeric(df[col].astype(str).str.replace(',', ''), errors='coerce').fillna(0)156        157        # Create data context158        data_details = []159        data_details.append(f"## LinkedIn Post Analysis Data\n")160        data_details.append(f"General Statistics:")161        data_details.append(f"- Total posts analyzed: {len(df)}")162        163        # Add engagement metrics including Engagement_rate if it exists164        metrics = {165            'Reactions': 'reactions',166            'Comment': 'comments', 167            'Reposts': 'reposts',168            'Engagement_rate': 'Engagement rate'169        }170        171        for col, label in metrics.items():172            if col in df.columns:173                data_details.append(f"\n{label.title()} Metrics:")174                data_details.append(f"- Highest {label}: {df[col].max():.1f}")175                data_details.append(f"- Average {label}: {df[col].mean():.1f}")176                data_details.append(f"- Median {label}: {df[col].median():.1f}")177                178                # Top posts by this metric179                data_details.append(f"\nTop Posts by {label.title()}:")180                top_posts = df.nlargest(3, col)181                for i, row in top_posts.iterrows():182                    title = row.get('Title', 'Untitled post')183                    value = row.get(col, 0)184                    data_details.append(f"- {title} ({value:.1f} {label})")185        186        # Rest of your chat function...187        188    except Exception as e:189        print(f"Chat error: {str(e)}")190        import traceback191        traceback.print_exc()192        193        error_message = f"Error processing question: {str(e)}"194        195        # Return error in the correct format196        user_message = {"role": "user", "content": question}197        assistant_message = {"role": "assistant", "content": error_message}198        199        if chat_history is None:200            return [user_message, assistant_message]201        else:202            return chat_history + [user_message, assistant_message]203 204def analyze_characteristics(df, column_name, cutoff=75):205    try:206        # Make sure the column exists207        if column_name not in df.columns:208            return f"Column '{column_name}' not found"209        210        # Create a copy to avoid modifying the original211        analysis_df = df.copy()212        213        # Ensure column is numeric214        analysis_df[column_name] = pd.to_numeric(analysis_df[column_name], errors='coerce')215        216        # Calculate percentiles first217        percentile_col = f"{column_name}_Percentile"218        if percentile_col not in analysis_df.columns:219            # Add the percentile column if it doesn't exist220            analysis_df[percentile_col] = analysis_df[column_name].rank(pct=True) * 100221        222        # Identify high-performing posts223        high_performing = analysis_df[analysis_df[percentile_col] > cutoff]224        225        if len(high_performing) == 0:226            return f"No posts above {cutoff}th percentile for {column_name}"227        228        # Generate insights229        insights = []230        231        # Analyze topics232        if 'Topic' in analysis_df.columns:233            top_topics = high_performing['Topic'].value_counts().head(3)234            insights.append(f"Top topics: {', '.join([f'{topic} ({count})' for topic, count in top_topics.items()])}")235        236        # Analyze tones237        if 'Writing Tone' in analysis_df.columns:238            top_tones = high_performing['Writing Tone'].value_counts().head(3)239            insights.append(f"Top tones: {', '.join([f'{tone} ({count})' for tone, count in top_tones.items()])}")240        241        # Analyze structure242        if 'Formatting' in analysis_df.columns:243            structure_counts = high_performing['Formatting'].str.split('|', expand=True)[0].str.strip().value_counts()244            top_structures = structure_counts.head(3)245            insights.append(f"Top structures: {', '.join([f'{struct} ({count})' for struct, count in top_structures.items()])}")246        247        # Return formatted insights248        return "\n".join(insights)249        250    except Exception as e:251        return f"Error in analyze_characteristics for {column_name}: {str(e)}"252 253# Add these imports to the top of your main file254from langchain_experimental.agents import create_pandas_dataframe_agent255from langchain_openai import OpenAI256from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder257from langchain.memory import ConversationBufferMemory258from langchain_core.messages import AIMessage, HumanMessage259import textwrap260 261def setup_langchain_agent(df):262    """Set up a LangChain agent that can perform calculations and analysis on the DataFrame"""263    try:264        # Create a Pandas DataFrame agent265        llm = ChatOpenAI(266            model_name="gpt-4",267            temperature=0.2268        )269        agent = create_pandas_dataframe_agent(270            llm, 271            df, 272            verbose=True,273            allow_dangerous_code=True,  # Add this line to explicitly allow code execution274            handle_parsing_errors=True,275            include_df_in_prompt=True,276            prefix="""You are an expert data analyst specializing in LinkedIn post analytics.277            You have access to a DataFrame with LinkedIn post data and metrics.278            You can perform calculations, statistical analysis, and answer questions about the data.279            Analyze the data carefully and provide detailed insights backed by specific metrics.280            281            The DataFrame includes the following columns:282            - Full Post: The complete text of the LinkedIn post283            - Reactions: Number of reactions (likes etc.) received284            - Comment: Number of comments received285            - Reposts: Number of reposts/shares286            - Word Count: Number of words in the post287            - Flesch Reading Ease: Readability score288            - Topic: The main topic category of the post289            - Writing Tone: The tone used in the post290            - Intent: The purpose of the post (Inform, Convince, etc.)291            - Formatting: Structure and formatting characteristics292            """293        )294        return agent295    except Exception as e:296        print(f"Error setting up LangChain agent: {str(e)}")297        return None298 299def chat_with_data_enhanced(question, df, chat_history=None, summary=None):300    """Enhanced version of chat_with_data that uses OpenAI for more natural responses"""301    try:302        # Initialize chat history if None303        if chat_history is None:304            chat_history = []305 306        if df is None:307            return [308                [question, "Please analyze a file first."]309            ]310        311        # Debug what we received312        print(f"\nAccessing dataframe with {len(df)} rows and {len(df.columns)} columns")313        print(f"Available columns: {df.columns.tolist()}")314        315        # Create the agent316        agent_executor = setup_enhanced_analysis_agent(df)317        if agent_executor is None:318            return [319                [question, "Failed to initialize analytics system. Please try again."]320            ]321        322        # Prepare context to include with the question323        context = ""324        325        # Add summary if available326        if summary:327            summary_brief = summary[:500] + "..." if len(summary) > 500 else summary328            context += f"Summary of previous analysis:\n{summary_brief}\n\n"329            330        # Enhanced question with context if needed331        enhanced_question = f"{context}Based on the LinkedIn post data, {question}"332        333        # Format chat history for the agent - works with both formats334        formatted_history = []335        for msg in chat_history:336            if isinstance(msg, list) and len(msg) == 2:337                # Old format: [user_msg, assistant_msg]338                user_msg, assistant_msg = msg339                formatted_history.append(("human", user_msg))340                formatted_history.append(("ai", assistant_msg))341            elif isinstance(msg, dict) and "role" in msg and "content" in msg:342                # New format: {"role": "...", "content": "..."}343                role = msg["role"]344                content = msg["content"]345                if role == "user":346                    formatted_history.append(("human", content))347                elif role == "assistant":348                    formatted_history.append(("ai", content))349        350        try:351            # Use the agent to answer the question352            response = agent_executor.invoke({"input": enhanced_question, "chat_history": formatted_history})353            answer = response.get("output", "I couldn't generate a response. Please try asking your question differently.")354            355            # Ensure the answer is a string356            if not isinstance(answer, str):357                answer = str(answer)358            359            # Return in a consistent format that works with both Gradio versions360            return [361                [question, answer]362            ]363            364        except Exception as agent_error:365            print(f"Agent error: {str(agent_error)}")366            return [367                [question, f"I encountered an error while processing your question: {str(agent_error)}"]368            ]369        370    except Exception as e:371        print(f"Chat error: {str(e)}")372        import traceback373        traceback.print_exc()374        375        return [376            [question, f"Error processing question: {str(e)}"]377        ]378 379def analyze_correlations(df, metric1, metric2):380    """Analyze the correlation between two metrics in the DataFrame"""381    try:382        # Ensure the columns exist383        if metric1 not in df.columns or metric2 not in df.columns:384            return f"One or both metrics not found: {metric1}, {metric2}"385        386        # Create a copy to avoid modifying the original387        analysis_df = df.copy()388        389        # Ensure columns are numeric390        for col in [metric1, metric2]:391            analysis_df[col] = pd.to_numeric(analysis_df[col], errors='coerce')392        393        # Drop rows with NaN values394        analysis_df = analysis_df.dropna(subset=[metric1, metric2])395        396        # Calculate correlation397        correlation = analysis_df[metric1].corr(analysis_df[metric2])398        399        # Interpret the correlation400        interpretation = ""401        if abs(correlation) < 0.3:402            interpretation = "weak or no linear relationship"403        elif abs(correlation) < 0.7:404            interpretation = "moderate linear relationship"405        else:406            interpretation = "strong linear relationship"407        408        # Direction409        direction = "positive" if correlation > 0 else "negative"410        411        return f"The correlation between {metric1} and {metric2} is {correlation:.3f}, indicating a {direction} {interpretation}."412    413    except Exception as e:414        return f"Error analyzing correlation: {str(e)}"415 416def segment_analysis(df, column, segments=3):417    """Perform segmented analysis on a specific metric"""418    try:419        # Ensure the column exists420        if column not in df.columns:421            return f"Column not found: {column}"422        423        # Create a copy to avoid modifying the original424        analysis_df = df.copy()425        426        # Ensure column is numeric427        analysis_df[column] = pd.to_numeric(analysis_df[column], errors='coerce')428        429        # Create segments430        analysis_df['Segment'] = pd.qcut(analysis_df[column], segments, labels=False)431        432        # Group by segments and calculate metrics433        segment_analysis = []434        435        for i in range(segments):436            segment_df = analysis_df[analysis_df['Segment'] == i]437            438            # Skip if segment is empty439            if len(segment_df) == 0:440                continue441                442            segment_stats = {443                'Segment': f"Segment {i+1}",444                'Range': f"{segment_df[column].min():.1f} - {segment_df[column].max():.1f}",445                'Count': len(segment_df),446                'Avg Reactions': segment_df['Reactions'].mean() if 'Reactions' in df.columns else None,447                'Avg Comments': segment_df['Comment'].mean() if 'Comment' in df.columns else None,448                'Avg Reposts': segment_df['Reposts'].mean() if 'Reposts' in df.columns else None,449            }450            451            # Add topic analysis if available452            if 'Topic' in df.columns:453                top_topics = segment_df['Topic'].value_counts().head(3)454                segment_stats['Top Topics'] = ', '.join([f"{t} ({c})" for t, c in top_topics.items()])455            456            # Add tone analysis if available457            if 'Writing Tone' in df.columns:458                top_tones = segment_df['Writing Tone'].value_counts().head(3)459                segment_stats['Top Tones'] = ', '.join([f"{t} ({c})" for t, c in top_tones.items()])460                461            segment_analysis.append(segment_stats)462        463        # Format results464        results = f"Segmented Analysis of {column}:\n\n"465        466        for segment in segment_analysis:467            results += f"### {segment['Segment']} ({segment['Range']})\n"468            results += f"- Posts: {segment['Count']}\n"469            470            if segment['Avg Reactions'] is not None:471                results += f"- Avg Reactions: {segment['Avg Reactions']:.1f}\n"472                473            if segment['Avg Comments'] is not None:474                results += f"- Avg Comments: {segment['Avg Comments']:.1f}\n"475                476            if segment['Avg Reposts'] is not None:477                results += f"- Avg Reposts: {segment['Avg Reposts']:.1f}\n"478                479            if 'Top Topics' in segment:480                results += f"- Top Topics: {segment['Top Topics']}\n"481                482            if 'Top Tones' in segment:483                results += f"- Top Tones: {segment['Top Tones']}\n"484                485            results += "\n"486        487        return results488    489    except Exception as e:490        return f"Error performing segment analysis: {str(e)}"491    492    493 494def chat_with_data_safe(question, df, chat_history=None, summary=None):495    """Safe version of chat_with_data that uses predefined analysis tools"""496    try:497        # Initialize chat history if None498        if chat_history is None:499            chat_history = []500        501        if df is None:502            return [503                [question, "Please analyze a file first."]504            ]505        506        # Debug what we received507        print(f"\nAccessing dataframe with {len(df)} rows and {len(df.columns)} columns")508        print(f"Available columns: {df.columns.tolist()}")509        510        # Create the agent511        agent_executor = setup_safe_analysis_agent(df)512        if agent_executor is None:513            return [514                [question, "Failed to initialize analytics system. Please try again."]515            ]516        517        # Prepare context to include with the question518        context = ""519        520        # Add summary if available521        if summary:522            summary_brief = summary[:500] + "..." if len(summary) > 500 else summary523            context += f"Summary of previous analysis:\n{summary_brief}\n\n"524            525        # Enhanced question with context if needed526        enhanced_question = f"{context}Based on the LinkedIn post data, {question}"527        528        # Format chat history for the agent - works with both formats529        formatted_history = []530        for msg in chat_history:531            if isinstance(msg, list) and len(msg) == 2:532                # Old format: [user_msg, assistant_msg]533                user_msg, assistant_msg = msg534                formatted_history.append(("human", user_msg))535                formatted_history.append(("ai", assistant_msg))536            elif isinstance(msg, dict) and "role" in msg and "content" in msg:537                # New format: {"role": "...", "content": "..."}538                role = msg["role"]539                content = msg["content"]540                if role == "user":541                    formatted_history.append(("human", content))542                elif role == "assistant":543                    formatted_history.append(("ai", content))544        545        try:546            # Use the agent to answer the question547            response = agent_executor.invoke({"input": enhanced_question, "chat_history": formatted_history})548            answer = response.get("output", "I couldn't generate a response. Please try asking your question differently.")549            550            # Ensure the answer is a string551            if not isinstance(answer, str):552                answer = str(answer)553            554            # Return in a consistent format that works with both Gradio versions555            return [556                [question, answer]557            ]558            559        except Exception as agent_error:560            print(f"Agent error: {str(agent_error)}")561            return [562                [question, f"I encountered an error while processing your question: {str(agent_error)}"]563            ]564        565    except Exception as e:566        print(f"Chat error: {str(e)}")567        import traceback568        traceback.print_exc()569        570        return [571            [question, f"Error processing question: {str(e)}"]572        ]573 574    575    # Create the Gradio interface576    with gr.Blocks() as demo:577        chatbot = gr.Chatbot(show_label=False)578        msg = gr.Textbox(label="Ask questions about the analysis results")579        clear = gr.Button("Clear")580        581        msg.submit(chat_with_agent, [msg, chatbot], [chatbot])582        clear.click(lambda: [], None, chatbot, queue=False)583    584    return demo585 586def chat_handler(message, df, history, summary, use_enhanced_mode=False):587    # Only process if there's a message588    if message:589        if use_enhanced_mode:590            chat_response = chat_with_data_enhanced(message, df, history, summary)591        else:592            chat_response = chat_with_data_safe(message, df, history, summary)593        594        # Initialize history if None595        if history is None:596            history = []597        598        # Check if we're using the newer Gradio with messages format599        try:600            # Check Gradio version first601            gradio_version = gr.__version__602            using_messages_format = False603            604            try:605                from packaging import version606                if version.parse(gradio_version) >= version.parse("4.44.0"):607                    using_messages_format = True608            except ImportError:609                # If packaging is not available, do a simple string comparison610                using_messages_format = gradio_version >= "4.44.0"611        except:612            using_messages_format = False613            614        # Process chat response615        if isinstance(chat_response, list) and len(chat_response) > 0:616            # Handle the new message pair617            if isinstance(chat_response[0], list) and len(chat_response[0]) == 2:618                # Format is [[user_msg, assistant_msg]]619                user_msg, assistant_msg = chat_response[0]620                621                if using_messages_format:622                    # Convert history to messages format if it's not already623                    if history and not (isinstance(history[0], dict) and "role" in history[0]):624                        converted_history = []625                        for msg_pair in history:626                            if isinstance(msg_pair, list) and len(msg_pair) == 2:627                                user, assistant = msg_pair628                                converted_history.append({"role": "user", "content": user})629                                converted_history.append({"role": "assistant", "content": assistant})630                        history = converted_history631                    632                    # New format (Gradio 4.44+)633                    new_messages = [634                        {"role": "user", "content": user_msg},635                        {"role": "assistant", "content": assistant_msg}636                    ]637                    638                    # Return updated history in the messages format639                    return "", history + new_messages640                else:641                    # Old format (Gradio 4.19 or earlier)642                    # Ensure history is in the old format if it's not already643                    if history and isinstance(history[0], dict) and "role" in history[0]:644                        converted_history = []645                        for i in range(0, len(history), 2):646                            if i+1 < len(history):647                                user = history[i].get("content", "")648                                assistant = history[i+1].get("content", "")649                                converted_history.append([user, assistant])650                        history = converted_history651                    652                    return "", history + [[user_msg, assistant_msg]]653        654    return message, history  # Keep existing state if no message655 656def main():657    demo = gr.Blocks(658        title="LinkedIn Post Analyzer",659        css="footer {display: none !important;}"660    )661    662    with demo:663        gr.Markdown("# LinkedIn Post Analyzer")664        gr.Markdown("Upload your LinkedIn posts data (CSV or Excel) to analyze content performance and get AI-powered insights.")665 666        processed_data_state = gr.State()667        summary_state = gr.State()668 669        with gr.Row():670            with gr.Column():671                file_input = gr.File(label="Upload CSV or Excel", file_types=['.csv', '.xlsx', '.xls'])672                analyze_button = gr.Button("Analyze")673            with gr.Column():674                status = gr.Textbox(label="Status", value="Ready to analyze...")675                updated_file_download = gr.File(label="Download Updated Excel")676                summary_download = gr.File(label="Download Summary Markdown")677 678        with gr.Row():679            with gr.Column():680                # Initialize chatbot with empty list - compatible with different Gradio versions681                try:682                    # Check Gradio version first683                    gradio_version = gr.__version__684                    supports_messages = False685                    686                    try:687                        from packaging import version688                        if version.parse(gradio_version) >= version.parse("4.44.0"):689                            supports_messages = True690                    except ImportError:691                        # If packaging is not available, do a simple string comparison692                        supports_messages = gradio_version >= "4.44.0"693                    694                    if supports_messages:695                        # Use newer syntax with messages type696                        chatbot = gr.Chatbot(697                            show_label=False,698                            type='messages',699                            value=[]700                        )701                    else:702                        # Use older syntax without type parameter703                        chatbot = gr.Chatbot(704                            show_label=False,705                            value=[]706                        )707                except Exception as e:708                    print(f"Error initializing chatbot: {str(e)}")709                    # Fall back to the older syntax if anything goes wrong710                    chatbot = gr.Chatbot(711                        show_label=False,712                        value=[]713                    )714                msg = gr.Textbox(label="Ask questions about the analysis results")715                716                enhanced_mode = gr.Checkbox(label="Use Enhanced Analysis Mode", value=False)717                718                gr.Markdown("### Example Questions:")719                with gr.Row():720                    q1 = gr.Button("What are the characteristics of high-performing posts?")721                    q2 = gr.Button("Which content structures and formats work best?")722                    q3 = gr.Button("What will be your top 3 recommendations to improve my engagement?")723                    q4 = gr.Button("What is the breakdown of posts across topics for the top 10% performing posts?")724                725                clear = gr.Button("Clear Chat")726 727        # Example question handlers728        def set_message(question):729            # Return empty history since the message will be processed by chat_handler730            return question, []731 732        q1.click(733            fn=lambda: set_message("What are the characteristics of high-performing posts?"),734            inputs=None,735            outputs=[msg, chatbot]736        )737        q2.click(738            fn=lambda: set_message("Which content structures and formats work best?"),739            inputs=None,740            outputs=[msg, chatbot]741        )742        q3.click(743            fn=lambda: set_message("What will be your top 3 recommendations to improve my engagement?"),744            inputs=None,745            outputs=[msg, chatbot]746        )747        q4.click(748            fn=lambda: set_message("What is the breakdown of posts across topics for the top 10% performing posts?"),749            inputs=None,750            outputs=[msg, chatbot]751        )752 753        # Clear chat history - return empty list in correct format754        clear.click(lambda: (None, []), outputs=[msg, chatbot])755 756        # File analysis handler757        analyze_button.click(758            analyze_file,759            inputs=[file_input],760            outputs=[status, updated_file_download, summary_download, processed_data_state, summary_state]761        )762 763        # Message handler764        msg.submit(765            chat_handler,766            inputs=[msg, processed_data_state, chatbot, summary_state, enhanced_mode],767            outputs=[msg, chatbot]768        )769 770    return demo771 772if __name__ == "__main__":773    demo = main()774    if os.getenv('SPACE_ID'):775        # We're running on HF Spaces776        demo.launch(777            server_name="0.0.0.0",778            server_port=7860,779            share=False,780            favicon_path="https://huggingface.co/front/assets/huggingface_logo-noborder.svg"781        )782    else:783        # We're running locally784        demo.launch(share=True)