NifemiAluko/linkedin-post-analyzer
0
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) 