CoolFace
Apppublic

bacancydataprophets/AI-Generated_FAQs

sourceHugging Facemitupdated 1y agoView on Hugging Face
0likes
app.py665 linesDownload Raw Back to root
1import re2import os3import streamlit as st4import pandas as pd5import json6from typing import List, Dict7from groq import Groq8import time9from dotenv import load_dotenv10import math11from collections import Counter12 13# Load environment variables from .env file14load_dotenv()15 16reviews_data = {}17 18# Configure the Streamlit page19st.set_page_config(20    page_title="AI FAQ Generator",21    page_icon="๐Ÿค–",22    layout="wide",23    initial_sidebar_state="expanded"24)25 26class OptimizedFAQGenerator:27    def __init__(self, api_key: str):28        """Initialize the FAQ Generator with Groq API key."""29        self.client = Groq(api_key=api_key)30        self.model = "llama3-8b-8192"  # Fast and efficient model31        self.batch_size = 100  # Process reviews in batches of 10032        self.max_text_length = 3000  # Maximum text length per API call33    34    def chunk_reviews_by_size(self, reviews_data: List[Dict], max_chars: int = 3000) -> List[List[Dict]]:35        """Chunk reviews by character count to stay within API limits."""36        chunks = []37        current_chunk = []38        current_length = 039        40        for review in reviews_data:41            review_text = review.get('review_text', '')42            review_length = len(review_text) + 50  # Add buffer for formatting43            44            # If adding this review would exceed the limit, start a new chunk45            if current_length + review_length > max_chars and current_chunk:46                chunks.append(current_chunk)47                current_chunk = [review]48                current_length = review_length49            else:50                current_chunk.append(review)51                current_length += review_length52        53        # Add the last chunk if it has content54        if current_chunk:55            chunks.append(current_chunk)56        57        return chunks58    59    def extract_keywords_from_batch(self, review_batch: List[Dict]) -> List[str]:60        """Extract keywords from a batch of reviews."""61        # Combine review texts from the batch62        batch_text = " ".join([review.get('review_text', '') for review in review_batch if review.get('review_text')])63        64        # Truncate if too long65        if len(batch_text) > self.max_text_length:66            batch_text = batch_text[:self.max_text_length]67        68        prompt = f"""69        Analyze these customer reviews and extract SEO keywords and phrases for a business website.70        71        IMPORTANT RULES:72        1. DO NOT include any specific brand names or business names73        2. Focus on generic industry terms and services74        3. Extract keywords that any similar business could use75        4. Focus on customer pain points and solutions76        77        Extract keywords for:78        - Products and services mentioned (generic terms only)79        - Common customer concerns and questions80        - Industry terminology81        - Customer experience themes82        - Service quality aspects83        84        Reviews:85        {batch_text}86        87        Return exactly 15 relevant SEO keywords/phrases, one per line, without numbering or bullets.88        Use generic terms that any business in this industry could use.89        """90        91        try:92            response = self.client.chat.completions.create(93                messages=[{"role": "user", "content": prompt}],94                model=self.model,95                temperature=0.3,96                max_tokens=40097            )98            99            keywords = [kw.strip() for kw in response.choices[0].message.content.strip().split('\n') if kw.strip()]100            return keywords[:15]  # Limit to 15 keywords per batch101            102        except Exception as e:103            st.error(f"Error extracting keywords from batch: {str(e)}")104            return []105    106    def extract_seo_keywords(self, reviews_data: List[Dict]) -> List[str]:107        """Extract SEO keywords from all reviews using batch processing."""108        st.info(f"Processing {len(reviews_data)} reviews in batches...")109        110        # Create progress bar111        progress_bar = st.progress(0)112        status_text = st.empty()113        114        # Chunk reviews by character count115        review_chunks = self.chunk_reviews_by_size(reviews_data, self.max_text_length)116        117        all_keywords = []118        119        for i, chunk in enumerate(review_chunks):120            status_text.text(f"Processing batch {i+1}/{len(review_chunks)} ({len(chunk)} reviews)...")121            122            # Extract keywords from this batch123            batch_keywords = self.extract_keywords_from_batch(chunk)124            all_keywords.extend(batch_keywords)125            126            # Update progress127            progress_bar.progress((i + 1) / len(review_chunks))128            129            # Small delay to avoid rate limiting130            time.sleep(0.5)131        132        # Count keyword frequency and get top keywords133        keyword_counts = Counter(all_keywords)134        top_keywords = [kw for kw, count in keyword_counts.most_common(25)]135        136        progress_bar.empty()137        status_text.empty()138        139        st.success(f"Extracted {len(top_keywords)} unique keywords from {len(review_chunks)} batches")140        141        return top_keywords142    143    def get_review_insights(self, reviews_data: List[Dict]) -> Dict:144        """Extract insights from reviews for better FAQ generation."""145        # Sample reviews for analysis146        sample_size = min(50, len(reviews_data))147        sample_reviews = reviews_data[:sample_size]148        149        insights = {150            'total_reviews': len(reviews_data),151            'avg_rating': sum(int(r.get('rating', 0)) for r in reviews_data) / len(reviews_data),152            'positive_reviews': sum(1 for r in reviews_data if int(r.get('rating', 0)) >= 4),153            'common_themes': [],154            'pain_points': [],155            'positive_aspects': []156        }157        158        # Analyze positive vs negative reviews159        positive_reviews = [r for r in sample_reviews if int(r.get('rating', 0)) >= 4]160        negative_reviews = [r for r in sample_reviews if int(r.get('rating', 0)) <= 2]161        162        insights['sample_positive'] = positive_reviews[:5]163        insights['sample_negative'] = negative_reviews[:3]164        165        return insights166    167    def clean_json_response(self, response_text: str) -> str:168        """Clean and extract JSON from AI response."""169        # Remove markdown code blocks170        response_text = re.sub(r'```json\s*', '', response_text)171        response_text = re.sub(r'```\s*', '', response_text)172        173        # Find the JSON array174        json_start = response_text.find('[')175        json_end = response_text.rfind(']') + 1176        177        if json_start != -1 and json_end > json_start:178            json_content = response_text[json_start:json_end]179            180            # Clean common JSON issues181            json_content = re.sub(r'\n\s*', ' ', json_content)  # Remove newlines and extra spaces182            json_content = re.sub(r'"\s*,\s*"', '", "', json_content)  # Fix spacing around commas183            json_content = re.sub(r'}\s*,\s*{', '}, {', json_content)  # Fix object separators184            185            return json_content186        187        return None188    189    def generate_faqs(self, keywords: List[str], reviews_data: List[Dict], num_faqs: int = 20) -> List[Dict]:190        """Generate FAQs based on SEO keywords and review insights."""191        192        # Get review insights193        insights = self.get_review_insights(reviews_data)194        195        # Create sample review context (limit to prevent token overflow)196        sample_reviews = []197        for review in insights['sample_positive']:198            sample_reviews.append(f"Rating: {review.get('rating', 'N/A')}/5 - {review.get('review_text', '')[:150]}...")199        200        for review in insights['sample_negative']:201            sample_reviews.append(f"Rating: {review.get('rating', 'N/A')}/5 - {review.get('review_text', '')[:150]}...")202        203        sample_context = "\n".join(sample_reviews[:8])  # Limit to 8 samples204        205        # Limit FAQs to maximum of 30206        num_faqs = min(num_faqs, 15)207        208        prompt = f"""209        Based on the following SEO keywords and customer review insights, generate exactly {num_faqs} comprehensive FAQ pairs for a business website.210 211        CRITICAL REQUIREMENTS:212        1. DO NOT use any specific brand names or business names in questions or answers213        2. Use generic terms like "our store", "our business", "our team", "our services"214        3. Focus on universal customer concerns and solutions215 216        SEO Keywords: {', '.join(keywords[:20])}217 218        Business Insights:219        - Total Reviews Analyzed: {insights['total_reviews']}220        - Average Rating: {insights['avg_rating']:.1f}/5221        - Positive Reviews: {insights['positive_reviews']}/{insights['total_reviews']}222 223        Sample Customer Feedback:224        {sample_context}225 226        IMPORTANT: Respond with ONLY a valid JSON array. No additional text or markdown.227 228        Format:229        [230          {{231            "question": "Why should I choose your business for my needs?",232            "answer": "Our experienced team provides personalized service with attention to detail. We focus on understanding your specific requirements and delivering solutions that exceed expectations, backed by our commitment to quality and customer satisfaction."233          }}234        ]235        """236        237        try:238            st.info("Generating FAQs with AI...")239            240            response = self.client.chat.completions.create(241                messages=[242                    {"role": "system", "content": "You are a helpful assistant that generates brand-neutral JSON responses for FAQ content. Always respond with valid JSON only, without any brand names."},243                    {"role": "user", "content": prompt}244                ],245                model=self.model,246                temperature=0.2,  # Lower temperature for more consistent output247                max_tokens=3000   # Increased for more FAQs248            )249            250            # Get the response content251            content = response.choices[0].message.content.strip()252            253            # Clean and extract JSON254            json_content = self.clean_json_response(content)255            256            if json_content:257                try:258                    faqs = json.loads(json_content)259                    # Validate that it's a list of dictionaries with required keys260                    if isinstance(faqs, list) and all(isinstance(faq, dict) and 'question' in faq and 'answer' in faq for faq in faqs):261                        # Limit to requested number262                        return faqs[:num_faqs]263                    else:264                        st.warning("Invalid FAQ format received, using fallback")265                        return self._get_fallback_faqs(num_faqs)266                except json.JSONDecodeError as e:267                    st.error(f"JSON parsing error: {str(e)}")268                    return self._get_fallback_faqs(num_faqs)269            else:270                st.warning("Could not extract JSON from response, using fallback")271                return self._get_fallback_faqs(num_faqs)272                273        except Exception as e:274            st.error(f"Error generating FAQs: {str(e)}")275            return self._get_fallback_faqs(num_faqs)276    277    def _get_fallback_faqs(self, num_faqs: int = 20) -> List[Dict]:278        """Fallback FAQs if API fails - brand neutral and organized by categories."""279        base_faqs = [280            # Why choose us questions281            {282                "question": "Why should I choose your business over competitors?",283                "answer": "Our experienced team provides personalized service with attention to detail and a commitment to customer satisfaction. We take time to understand your specific needs and work with you throughout the entire process to ensure you're completely happy with the results."284            },285            {286                "question": "What makes your customer service different?",287                "answer": "We pride ourselves on patient, welcoming service where customers never feel rushed. Our team focuses on creating a comfortable experience while providing expert guidance to help you make the best decisions for your needs."288            },289            {290                "question": "How experienced is your team?",291                "answer": "Our team consists of experienced professionals who are passionate about helping customers achieve their goals. We stay updated with the latest trends and techniques to provide you with the best possible service and advice."292            },293            294            # Problem-solving questions295            {296                "question": "How do you help customers who feel overwhelmed by choices?",297                "answer": "Our knowledgeable staff guides you through the selection process based on your preferences, budget, and specific needs. We take time to understand your vision and narrow down options so you can make decisions with confidence."298            },299            {300                "question": "What if I'm not satisfied with the results?",301                "answer": "Customer satisfaction is our top priority. We work closely with you throughout the process and make adjustments as needed to ensure you're completely happy with the final outcome. Our team is committed to making things right."302            },303            {304                "question": "How do you handle sizing and fit issues?",305                "answer": "Our professional team provides expert fitting services and makes necessary adjustments to ensure perfect results. We take precise measurements and work with you through multiple fittings if needed to achieve the ideal fit."306            },307            308            # Service questions309            {310                "question": "What services do you offer besides your main products?",311                "answer": "In addition to our primary offerings, we provide professional consultation, customization services, and ongoing support. We also offer accessories and complementary products to complete your experience with us."312            },313            {314                "question": "Do you provide consultation services?",315                "answer": "Yes, we offer personalized consultations where our experts help you explore options, provide styling advice, and ensure you make choices that align with your vision and budget. These consultations are designed to make your experience as smooth as possible."316            },317            {318                "question": "What additional products and accessories do you carry?",319                "answer": "We offer a comprehensive selection of complementary products and accessories to complete your needs. Our team can help coordinate everything to ensure a cohesive and polished final result."320            },321            322            # Process questions323            {324                "question": "Do I need an appointment or can I walk in?",325                "answer": "While we welcome walk-ins when possible, we highly recommend scheduling an appointment to ensure you receive dedicated attention and personalized service. Appointments allow us to prepare for your visit and provide the best possible experience."326            },327            {328                "question": "How long does the typical process take?",329                "answer": "The timeline varies depending on your specific needs, but we work with you to establish realistic expectations from the start. Our team keeps you informed throughout the process and ensures everything is completed according to your schedule."330            },331            {332                "question": "What should I expect during my first visit?",333                "answer": "During your initial visit, we'll discuss your needs, preferences, and budget. Our team will guide you through available options, provide expert recommendations, and create a plan tailored to your specific requirements."334            },335            336            # Quality questions337            {338                "question": "How do you ensure quality in your products and services?",339                "answer": "We maintain high standards through careful selection of products, skilled craftsmanship, and thorough quality checks. Our experienced team pays attention to every detail to ensure you receive exceptional results that meet our quality standards."340            },341            {342                "question": "What is your experience with customers who have specific requirements?",343                "answer": "Our team has extensive experience working with diverse customer needs and preferences. We pride ourselves on our ability to accommodate special requirements and provide customized solutions that exceed expectations."344            },345            {346                "question": "How do you stay current with industry trends?",347                "answer": "Our team continuously educates themselves on the latest trends, techniques, and products in the industry. We attend training sessions and stay connected with industry developments to provide you with current options and expert advice."348            },349            350            # Additional comprehensive questions351            {352                "question": "What price ranges do you offer?",353                "answer": "We offer options across various price points to accommodate different budgets. Our team can help you find quality solutions within your budget and provide transparent pricing information upfront so you can make informed decisions."354            },355            {356                "question": "Do you offer payment plans or financing options?",357                "answer": "Yes, we understand that significant purchases require financial planning. We offer flexible payment options and financing plans to make our services more accessible and help you achieve your goals within your budget."358            },359            {360                "question": "How far in advance should I start planning?",361                "answer": "We recommend starting the process several months in advance to allow adequate time for consultation, selection, customization, and any necessary adjustments. Early planning ensures the best selection and reduces stress as your important date approaches."362            },363            {364                "question": "Do you work with customers who have time constraints?",365                "answer": "Absolutely! We understand that sometimes timelines are tight, and we're experienced in working efficiently to meet urgent deadlines. Our team will discuss your timeline and work diligently to accommodate your schedule while maintaining quality standards."366            },367            {368                "question": "What sets your customer experience apart?",369                "answer": "We focus on creating a welcoming, pressure-free environment where customers feel comfortable and supported. Our personalized approach, attention to detail, and commitment to customer satisfaction ensure that your experience with us is positive and memorable."370            },371            {372                "question": "How do you handle special requests or customizations?",373                "answer": "We welcome special requests and customizations as part of our personalized service approach. Our skilled team works with you to understand your vision and explore options for creating something unique that perfectly meets your specific needs and preferences."374            }375        ]376        377        # Return the requested number of FAQs, up to the available amount378        return base_faqs[:min(num_faqs, len(base_faqs))]379 380def load_sample_data():381    """Load sample data if no file is uploaded."""382    sample_data = [383        {384            "reviewer_name": "Customer A",385            "rating": 5,386            "date": "3 months ago",387            "review_text": "This past August, I went to the bridal store to look for my dream wedding dress and I found It! I was looking for an elegant, simple, and classic dress. The consultant was extremely helpful, patient, and sweet. The alterations team did a great job making sure I was happy with the alterations done to my dress.",388            "owner_response": "Thank you so much for taking the time to leave this excellent review!",389        },390        {391            "reviewer_name": "Customer B",392            "rating": 5,393            "date": "2 months ago",394            "review_text": "A very special shout out to the consultant who made my daughters dress shopping so special. Never rushed her and only everything to accommodate her until she found the right dress to say yes to.",395            "owner_response": "",396        },397        {398            "reviewer_name": "Customer C",399            "rating": 5,400            "date": "1 month ago",401            "review_text": "My wedding dress shopping experience was beyond amazing. The consultant was so wonderful to work with. Everyone was so sweet & welcoming when we walked in. She made me feel so comfortable as we tried on many different dresses.",402            "owner_response": "Thank you for the wonderful review!",403        }404    ]405    return sample_data406 407def main():408    st.title("๐Ÿค– AI FAQ Generator")409    st.markdown("Generate SEO-optimized, FAQs from customer reviews")410    411    # Sidebar for configuration412    with st.sidebar:413        st.header("โš™๏ธ Configuration")414        415        # API Key input416        api_key = os.getenv("GROQ_API_KEY")417        418        if not api_key:419            st.warning("Please enter your Groq API key to use AI features")420            st.markdown("Get your API key from [Groq Console](https://console.groq.com)")421        422        st.divider()423        424        # FAQ Configuration425        st.subheader("FAQ Settings")426        num_faqs = st.slider("Number of FAQs to generate", 5, 15, 10, 1, help="Select how many FAQs to generate based on the reviews")427        if num_faqs < 5:428            st.warning("Generating fewer than 5 FAQs may not provide enough coverage of customer concerns")429        elif num_faqs > 15:430            st.warning("Generating more than 15 FAQs may lead to less focused content")431        st.info(f"Will generate {num_faqs} FAQs")432        433        st.divider()434        435        # File upload436        uploaded_file = st.file_uploader(437            "Upload Reviews CSV", 438            type=['csv'],439            help="Upload a CSV file with customer reviews (supports large files)"440        )441        442        # Use sample data option443        use_sample = st.checkbox("Use sample data", value=False)444    445    # Load data446    if uploaded_file is not None:447        try:448            with st.spinner("Loading CSV file..."):449                df = pd.read_csv(uploaded_file)450                451                # Data cleaning452                df['rating'] = df['rating'].astype(str)453                df = df.drop(columns=['review_id', 'scraped_at'], axis=1, errors='ignore')454                455                # Remove empty reviews456                df = df.dropna(subset=['review_text'])457                df = df[df['review_text'].str.strip() != '']458                459                reviews_data = df.to_dict('records')460                461            st.success(f"โœ… Loaded {len(reviews_data)} reviews from uploaded file")462            463            # Show file statistics464            col1, col2, col3 = st.columns(3)465            with col1:466                st.metric("Total Reviews", len(reviews_data))467            with col2:468                avg_rating = sum(int(r.get('rating', 0)) for r in reviews_data if r.get('rating', '0').isdigit()) / len([r for r in reviews_data if r.get('rating', '0').isdigit()])469                st.metric("Average Rating", f"{avg_rating:.1f}")470            with col3:471                positive_reviews = sum(1 for r in reviews_data if r.get('rating', '0').isdigit() and int(r.get('rating', 0)) >= 4)472                st.metric("Positive Reviews", f"{positive_reviews}/{len(reviews_data)}")473                474        except Exception as e:475            st.error(f"Error loading file: {str(e)}")476            st.info("Please ensure your CSV has columns: 'review_text', 'rating'")477            reviews_data = load_sample_data()478    elif use_sample:479        reviews_data = load_sample_data()480        st.info("Using sample data for demonstration")481    else:482        st.warning("Please upload a CSV file or use sample data")483        return484    485    # Display data overview486    if reviews_data:487        with st.expander("๐Ÿ“Š Data Overview", expanded=False):488            # Sample reviews preview489            st.subheader("Sample Reviews")490            for i, review in enumerate(reviews_data[:3]):491                with st.container():492                    st.write(f"**{review.get('reviewer_name', 'Anonymous')}** - {review.get('rating', 'N/A')} โญ")493                    st.write(review.get('review_text', 'No review text')[:300] + "...")494                    if i < 2:  # Don't show divider after last item495                        st.divider()496    497    # Generate FAQs498    if api_key and reviews_data:499        st.header("๐Ÿš€ Generate AI-Powered FAQs")500        501        col1, col2 = st.columns(2)502        with col1:503            if st.button("๐Ÿค– Generate Keywords & FAQs with AI", type="primary"):504                start_time = time.time()505                506                with st.spinner("Processing large dataset..."):507                    # Initialize FAQ generator508                    faq_gen = OptimizedFAQGenerator(api_key)509                    510                    # Extract keywords with batch processing511                    st.info("๐Ÿ” Extracting SEO keywords from all reviews...")512                    keywords = faq_gen.extract_seo_keywords(reviews_data)513                    514                    # Store in session state515                    st.session_state.keywords = keywords516                    517                    # Generate FAQs518                    st.info("๐Ÿ“ Generating brand-neutral FAQs...")519                    faqs = faq_gen.generate_faqs(keywords, reviews_data, num_faqs)520                    521                    # Store in session state522                    st.session_state.faqs = faqs523                    524                    generation_time = time.time() - start_time525                    st.session_state.generation_time = generation_time526                    527                    st.success(f"โœ… Generated {len(faqs)} FAQs in {generation_time:.1f} seconds!")528        529        # with col2:530        #     if st.button("โšก Use Quick Fallback"):531        #         faq_gen = OptimizedFAQGenerator("")  # Empty API key for fallback532        #         st.session_state.keywords = ["customer service", "quality products", "professional consultation", "experienced team", "customer satisfaction"]533        #         st.session_state.faqs = faq_gen._get_fallback_faqs(num_faqs)534        #         st.info("Using pre-built content")535    536    # Display results537    if hasattr(st.session_state, 'keywords') and hasattr(st.session_state, 'faqs'):538        539        # Performance metrics540        if hasattr(st.session_state, 'generation_time'):541            st.info(f"โฑ๏ธ Generation completed in {st.session_state.generation_time:.1f} seconds")542        543        st.header("๐Ÿ“ˆ Extracted SEO Keywords")544        545        # Display keywords in a nice format546        keywords = st.session_state.keywords547        548        # Show keywords in columns549        cols = st.columns(3)550        for i, keyword in enumerate(keywords):551            with cols[i % 3]:552                st.markdown(f"`{keyword}`")553        554        st.header("โ“ Generated Brand-Neutral FAQs")555        st.info(f"Generated {len(st.session_state.faqs)} FAQs that can be used by any business in this industry")556        557        # Display FAQs with search functionality558        search_term = st.text_input("๐Ÿ” Search FAQs", placeholder="Enter keywords to filter FAQs...")559        560        faqs = st.session_state.faqs561        562        # Filter FAQs if search term is provided563        if search_term:564            filtered_faqs = [565                faq for faq in faqs 566                if search_term.lower() in faq.get('question', '').lower() 567                or search_term.lower() in faq.get('answer', '').lower()568            ]569            st.info(f"Showing {len(filtered_faqs)} FAQs matching '{search_term}'")570            faqs_to_show = filtered_faqs571        else:572            faqs_to_show = faqs573        574        # Display FAQs575        for i, faq in enumerate(faqs_to_show):576            with st.expander(f"FAQ {i+1}: {faq.get('question', 'No question')}", expanded=False):577                st.subheader("Question:")578                st.write(faq.get('question', 'No question'))579                st.subheader("Answer:")580                st.write(faq.get('answer', 'No answer'))581        582        # Export options583        st.header("๐Ÿ“ฅ Export Options")584        585        col1, col2, col3 = st.columns(3)586        587        with col1:588            # Export as JSON589            export_data = {590                "metadata": {591                    "generated_at": time.strftime("%Y-%m-%d %H:%M:%S"),592                    "total_reviews_analyzed": len(reviews_data),593                    "generation_time_seconds": getattr(st.session_state, 'generation_time', 0),594                    "brand_neutral": True595                },596                "keywords": keywords,597                "faqs": faqs598            }599            600            st.download_button(601                label="๐Ÿ“„ Download JSON",602                data=json.dumps(export_data, indent=2),603                file_name="faqs.json",604                mime="application/json"605            )606        607        with col2:608            # Export as CSV609            faq_df = pd.DataFrame(faqs)610            csv_data = faq_df.to_csv(index=False)611            612            st.download_button(613                label="๐Ÿ“Š Download CSV",614                data=csv_data,615                file_name="faqs.csv",616                mime="text/csv"617            )618        619        with col3:620            # Export as HTML621            html_content = f"""622            <!DOCTYPE html>623            <html>624            <head>625                <title>Brand-Neutral FAQs</title>626                <style>627                    body {{ font-family: Arial, sans-serif; margin: 40px; line-height: 1.6; }}628                    .faq {{ margin-bottom: 30px; border-left: 4px solid #007bff; padding-left: 20px; }}629                    .question {{ font-weight: bold; font-size: 18px; color: #333; margin-bottom: 10px; }}630                    .answer {{ color: #666; }}631                    .header {{ background: #f8f9fa; padding: 20px; border-radius: 5px; margin-bottom: 30px; }}632                </style>633            </head>634            <body>635                <div class="header">636                    <h1>Brand-Neutral FAQs</h1>637                    <p><strong>Generated:</strong> {time.strftime("%Y-%m-%d %H:%M:%S")}</p>638                    <p><strong>Reviews Analyzed:</strong> {len(reviews_data)}</p>639                    <p><strong>Keywords:</strong> {', '.join(keywords[:10])}...</p>640                </div>641            """642            643            for i, faq in enumerate(faqs, 1):644                html_content += f"""645                <div class="faq">646                    <div class="question">{i}. {faq.get('question', '')}</div>647                    <div class="answer">{faq.get('answer', '')}</div>648                </div>649                """650            651            html_content += "</body></html>"652            653            st.download_button(654                label="๐ŸŒ Download HTML",655                data=html_content,656                file_name="faqs.html",657                mime="text/html"658            )659    660    # Footer661    st.markdown("---")662    st.markdown("**Features:** Batch processing for large datasets โ€ข Brand-neutral content โ€ข SEO optimization โ€ข Multiple export formats")663 664if __name__ == "__main__":665    main()