bacancydataprophets/AI-Generated_FAQs
0
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()