| | import gradio as gr |
| | import csv |
| | import io |
| | import json |
| | from typing import List, Dict, Any |
| | import math |
| |
|
| | def parse_csv_file(file_content: str) -> Dict[str, Any]: |
| | """Parse CSV content and extract guest information from name and message columns""" |
| | try: |
| | |
| | csv_file = io.StringIO(file_content) |
| | |
| | |
| | for delimiter in [',', ';', '\t']: |
| | try: |
| | csv_file.seek(0) |
| | reader = csv.DictReader(csv_file, delimiter=delimiter) |
| | |
| | |
| | headers = reader.fieldnames |
| | if not headers or len(headers) < 2: |
| | continue |
| | |
| | |
| | name_column = headers[0] |
| | message_column = headers[1] |
| | |
| | |
| | guests = [] |
| | for i, row in enumerate(reader): |
| | name = row.get(name_column, '').strip() |
| | title = row.get(message_column, '').strip() |
| | |
| | |
| | if name and title: |
| | guests.append({ |
| | 'id': i, |
| | 'name': name, |
| | 'title': title |
| | }) |
| | |
| | if guests: |
| | return {'success': True, 'guests': guests, 'total': len(guests)} |
| | |
| | except Exception as e: |
| | continue |
| | |
| | return {'success': False, 'error': 'Could not parse CSV file. Please ensure it has at least 2 columns (name and message).'} |
| | |
| | except Exception as e: |
| | return {'success': False, 'error': f'Error parsing CSV: {str(e)}'} |
| |
|
| | def arrange_guests_into_tables(guests: List[Dict]) -> List[List[Dict]]: |
| | """Arrange guests into tables of 10 people each with smart distribution""" |
| | if not guests: |
| | return [] |
| | |
| | |
| | categories = { |
| | 'tech': ['engineer', 'developer', 'programmer', 'software', 'tech', 'it', 'data', 'ai', 'ml', 'technology', 'scientist'], |
| | 'business': ['manager', 'director', 'ceo', 'founder', 'executive', 'business', 'strategy', 'operations', 'consultant', 'product'], |
| | 'creative': ['designer', 'creative', 'marketing', 'content', 'writer', 'artist', 'media', 'communications', 'strategist'], |
| | 'sales': ['sales', 'account', 'client', 'business development', 'partnership', 'account manager'], |
| | 'finance': ['finance', 'accounting', 'investment', 'banking', 'financial', 'analyst', 'cfo'], |
| | 'other': [] |
| | } |
| | |
| | categorized_guests = {cat: [] for cat in categories.keys()} |
| | |
| | |
| | for guest in guests: |
| | title_lower = guest['title'].lower() |
| | categorized = False |
| | |
| | for category, keywords in categories.items(): |
| | if category == 'other': |
| | continue |
| | if any(keyword in title_lower for keyword in keywords): |
| | categorized_guests[category].append(guest) |
| | categorized = True |
| | break |
| | |
| | if not categorized: |
| | categorized_guests['other'].append(guest) |
| | |
| | |
| | total_guests = len(guests) |
| | num_tables = (total_guests + 9) // 10 |
| | |
| | |
| | tables = [[] for _ in range(num_tables)] |
| | |
| | |
| | |
| | major_categories = ['tech', 'business', 'creative', 'sales'] |
| | |
| | for category in major_categories: |
| | guests_in_category = categorized_guests[category] |
| | if guests_in_category: |
| | |
| | for i, guest in enumerate(guests_in_category): |
| | table_index = i % num_tables |
| | if len(tables[table_index]) < 10: |
| | tables[table_index].append(guest) |
| | |
| | |
| | remaining_guests = [] |
| | for category in ['finance', 'other']: |
| | remaining_guests.extend(categorized_guests[category]) |
| | |
| | |
| | for table in tables: |
| | for guest in guests: |
| | if guest not in [g for table_guests in tables for g in table_guests]: |
| | if len(table) < 10: |
| | table.append(guest) |
| | break |
| | |
| | |
| | for guest in guests: |
| | if guest not in [g for table_guests in tables for g in table_guests]: |
| | for table in tables: |
| | if len(table) < 10: |
| | table.append(guest) |
| | break |
| | |
| | return tables |
| |
|
| | def create_circular_table_html(table_number: int, guests: List[Dict]) -> str: |
| | """Create HTML for a circular table with guests seated around it""" |
| | if not guests: |
| | return "" |
| | |
| | |
| | num_guests = len(guests) |
| | radius = 120 |
| | center_x, center_y = 150, 150 |
| | |
| | |
| | seats = [] |
| | for i in range(num_guests): |
| | angle = (2 * 3.14159 * i) / num_guests |
| | x = center_x + radius * math.cos(angle) |
| | y = center_y + radius * math.sin(angle) |
| | seats.append((x, y)) |
| | |
| | |
| | html = f""" |
| | <div class="table-container" style="margin: 20px; display: inline-block; text-align: center;"> |
| | <div class="table-circle" style=" |
| | width: 300px; |
| | height: 300px; |
| | position: relative; |
| | margin: 0 auto; |
| | "> |
| | <!-- Table circle --> |
| | <div style=" |
| | position: absolute; |
| | top: 50%; |
| | left: 50%; |
| | transform: translate(-50%, -50%); |
| | width: 240px; |
| | height: 240px; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | border-radius: 50%; |
| | border: 4px solid #4a5568; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | color: white; |
| | font-weight: bold; |
| | font-size: 18px; |
| | box-shadow: 0 4px 8px rgba(0,0,0,0.3); |
| | "> |
| | Table {table_number} |
| | </div> |
| | """ |
| | |
| | |
| | for i, (guest, (x, y)) in enumerate(zip(guests, seats)): |
| | |
| | initials = ''.join([name[0].upper() for name in guest['name'].split() if name]) |
| | if not initials: |
| | initials = guest['name'][:2].upper() |
| | |
| | html += f""" |
| | <div class="seat" style=" |
| | position: absolute; |
| | left: {x}px; |
| | top: {y}px; |
| | transform: translate(-50%, -50%); |
| | width: 40px; |
| | height: 40px; |
| | background: linear-gradient(135deg, #48bb78 0%, #38a169 100%); |
| | border-radius: 50%; |
| | border: 2px solid #2f855a; |
| | display: flex; |
| | align-items: center; |
| | justify-content: center; |
| | color: white; |
| | font-weight: bold; |
| | font-size: 12px; |
| | cursor: pointer; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.2); |
| | transition: all 0.3s ease; |
| | " |
| | title="{guest['name']} - {guest['title']}" |
| | onmouseover="this.style.transform='translate(-50%, -50%) scale(1.1)'; this.style.boxShadow='0 4px 8px rgba(0,0,0,0.4)';" |
| | onmouseout="this.style.transform='translate(-50%, -50%) scale(1)'; this.style.boxShadow='0 2px 4px rgba(0,0,0,0.2)';" |
| | > |
| | {initials} |
| | </div> |
| | """ |
| | |
| | html += """ |
| | </div> |
| | <div style="margin-top: 10px; font-weight: bold; color: #4a5568;"> |
| | {len(guests)} guests |
| | </div> |
| | </div> |
| | """ |
| | |
| | return html |
| |
|
| | def process_csv_and_arrange_tables(csv_content: str) -> str: |
| | """Main function to process CSV and arrange tables""" |
| | try: |
| | |
| | result = parse_csv_file(csv_content) |
| | |
| | if not result['success']: |
| | return f"β Error: {result['error']}" |
| | |
| | guests = result['guests'] |
| | total_guests = result['total'] |
| | |
| | if total_guests == 0: |
| | return "β No valid guests found in CSV. Please ensure you have at least 2 columns (name and message) with non-empty values." |
| | |
| | |
| | tables = arrange_guests_into_tables(guests) |
| | |
| | if not tables: |
| | return "β No tables could be created." |
| | |
| | |
| | output = f"π Successfully processed {total_guests} guests!\n\n" |
| | output += f"π Created {len(tables)} table(s) with smart distribution:\n\n" |
| | |
| | |
| | output += """ |
| | <style> |
| | .tables-container { |
| | display: flex; |
| | flex-wrap: wrap; |
| | justify-content: center; |
| | gap: 20px; |
| | margin: 20px 0; |
| | } |
| | .table-container { |
| | background: white; |
| | border-radius: 15px; |
| | padding: 20px; |
| | box-shadow: 0 4px 6px rgba(0,0,0,0.1); |
| | border: 1px solid #e2e8f0; |
| | } |
| | .guest-list { |
| | margin-top: 20px; |
| | text-align: left; |
| | max-width: 800px; |
| | margin-left: auto; |
| | margin-right: auto; |
| | } |
| | .guest-list h3 { |
| | color: #4a5568; |
| | border-bottom: 2px solid #e2e8f0; |
| | padding-bottom: 10px; |
| | margin-bottom: 15px; |
| | } |
| | .guest-item { |
| | background: #f7fafc; |
| | padding: 8px 12px; |
| | margin: 5px 0; |
| | border-radius: 6px; |
| | border-left: 4px solid #667eea; |
| | } |
| | .stats { |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | padding: 15px; |
| | border-radius: 10px; |
| | margin: 20px 0; |
| | text-align: center; |
| | } |
| | </style> |
| | """ |
| | |
| | |
| | output += f""" |
| | <div class="stats"> |
| | <h3>π Table Statistics</h3> |
| | <p>Total Guests: {total_guests} | Tables Created: {len(tables)}</p> |
| | </div> |
| | """ |
| | |
| | |
| | output += '<div class="tables-container">' |
| | for i, table in enumerate(tables, 1): |
| | output += create_circular_table_html(i, table) |
| | output += '</div>' |
| | |
| | |
| | output += '<div class="guest-list">' |
| | for i, table in enumerate(tables, 1): |
| | output += f'<h3>π½οΈ Table {i} ({len(table)} guests)</h3>' |
| | for j, guest in enumerate(table, 1): |
| | output += f'<div class="guest-item">{j}. <strong>{guest["name"]}</strong> - {guest["title"]}</div>' |
| | output += '<br>' |
| | output += '</div>' |
| | |
| | return output |
| | |
| | except Exception as e: |
| | return f"β Error processing request: {str(e)}" |
| |
|
| | def create_sample_csv() -> str: |
| | """Create a sample CSV for users to download""" |
| | sample_data = """name,message |
| | John Smith,Software Engineer at TechCorp |
| | Sarah Johnson,Marketing Director at Creative Agency |
| | Michael Brown,CEO of StartupXYZ |
| | Emily Davis,Data Scientist at AI Labs |
| | David Wilson,Product Manager at Innovation Inc |
| | Lisa Chen,UX Designer at Design Studio |
| | Robert Taylor,Sales Manager at SalesForce |
| | Amanda Rodriguez,Financial Analyst at Finance Corp |
| | James Lee,Content Strategist at Media Group |
| | Jennifer White,Business Development at Growth Co |
| | Alex Thompson,Data Engineer at DataFlow |
| | Maria Garcia,Creative Director at ArtStudio |
| | Chris Anderson,VP of Sales at SalesPro |
| | Rachel Kim,Product Designer at DesignHub |
| | Tom Wilson,Investment Analyst at Capital Corp""" |
| | return sample_data |
| |
|
| | |
| | with gr.Blocks( |
| | title="Party Planner - Guest Table Arranger", |
| | theme=gr.themes.Soft(), |
| | css=""" |
| | .gradio-container { |
| | max-width: 1400px !important; |
| | margin: 0 auto !important; |
| | } |
| | .main-header { |
| | text-align: center; |
| | margin-bottom: 2rem; |
| | background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); |
| | color: white; |
| | padding: 2rem; |
| | border-radius: 15px; |
| | margin-bottom: 2rem; |
| | } |
| | .sample-csv { |
| | background: #f0f8ff; |
| | padding: 1rem; |
| | border-radius: 8px; |
| | margin: 1rem 0; |
| | border-left: 4px solid #667eea; |
| | } |
| | .input-section { |
| | background: white; |
| | padding: 20px; |
| | border-radius: 10px; |
| | box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
| | margin-bottom: 20px; |
| | } |
| | """ |
| | ) as demo: |
| | |
| | gr.HTML(""" |
| | <div class="main-header"> |
| | <h1>π Party Planner - Guest Table Arranger</h1> |
| | <p>Upload your guest list CSV and let AI arrange them into optimal circular table seating!</p> |
| | </div> |
| | """) |
| | |
| | with gr.Row(): |
| | with gr.Column(scale=1): |
| | gr.HTML(""" |
| | <div class="input-section"> |
| | <h3>π How to use:</h3> |
| | <ol> |
| | <li>Prepare a CSV file with 2 columns: <strong>name</strong> and <strong>message/description</strong></li> |
| | <li>Upload your CSV file below</li> |
| | <li>Click "Arrange Tables" to see the smart circular seating arrangement</li> |
| | </ol> |
| | |
| | <div class="sample-csv"> |
| | <h4>π Sample CSV Format:</h4> |
| | <pre>name,message |
| | John Smith,Software Engineer at TechCorp |
| | Sarah Johnson,Marketing Director at Creative Agency |
| | Michael Brown,CEO of StartupXYZ</pre> |
| | </div> |
| | </div> |
| | """) |
| | |
| | sample_csv = gr.Textbox( |
| | label="Sample CSV Content", |
| | value=create_sample_csv(), |
| | lines=10, |
| | interactive=False |
| | ) |
| | |
| | download_sample = gr.Button("π₯ Download Sample CSV", variant="secondary") |
| | |
| | with gr.Column(scale=2): |
| | gr.HTML(""" |
| | <div class="input-section"> |
| | <h3>π Upload Your Guest List</h3> |
| | </div> |
| | """) |
| | |
| | csv_input = gr.Textbox( |
| | label="π Paste your CSV content here (or upload file below)", |
| | placeholder="Paste your CSV content here...", |
| | lines=10 |
| | ) |
| | |
| | file_input = gr.File( |
| | label="π Or upload CSV file", |
| | file_types=[".csv"], |
| | file_count="single" |
| | ) |
| | |
| | arrange_btn = gr.Button("π― Arrange Tables", variant="primary", size="lg") |
| | |
| | output = gr.HTML( |
| | label="π Table Arrangement Results", |
| | value="<div style='text-align: center; padding: 40px; color: #666;'><h3>Upload your guest list to see the circular table arrangement!</h3><p>π Your tables will appear as beautiful circles with guests seated around them</p></div>" |
| | ) |
| | |
| | |
| | def handle_file_upload(file): |
| | if file is None: |
| | return "" |
| | try: |
| | with open(file.name, 'r', encoding='utf-8') as f: |
| | content = f.read() |
| | return content |
| | except Exception as e: |
| | return f"Error reading file: {str(e)}" |
| | |
| | def download_sample_csv(): |
| | return create_sample_csv() |
| | |
| | |
| | file_input.change( |
| | fn=handle_file_upload, |
| | inputs=[file_input], |
| | outputs=[csv_input] |
| | ) |
| | |
| | arrange_btn.click( |
| | fn=process_csv_and_arrange_tables, |
| | inputs=[csv_input], |
| | outputs=[output] |
| | ) |
| | |
| | download_sample.click( |
| | fn=download_sample_csv, |
| | outputs=[csv_input] |
| | ) |
| |
|
| | |
| | if __name__ == "__main__": |
| | demo.launch() |