|
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() |