dpang commited on
Commit
7b84343
·
verified ·
1 Parent(s): 2d4b3d9

Upload 15 files

Browse files
Files changed (15) hide show
  1. DEPLOYMENT.md +318 -0
  2. Procfile +1 -0
  3. QUICKSTART.md +52 -0
  4. README.md +182 -14
  5. README_HF.md +116 -0
  6. app.py +8 -0
  7. app_gradio.py +296 -0
  8. application.py +201 -0
  9. codecommit-policy.json +63 -0
  10. data/guest_list.csv +122 -0
  11. requirements.txt +4 -0
  12. requirements_hf.txt +1 -0
  13. run.py +82 -0
  14. sample_guests.csv +11 -0
  15. templates/index.html +779 -0
DEPLOYMENT.md ADDED
@@ -0,0 +1,318 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # AWS Deployment Guide for Party Planner
2
+
3
+ This guide will walk you through deploying your Party Planner app on AWS using Elastic Beanstalk.
4
+
5
+ ## Prerequisites
6
+
7
+ 1. **AWS Account**: You need an active AWS account
8
+ 2. **AWS CLI**: Install and configure AWS CLI
9
+ 3. **EB CLI**: Install Elastic Beanstalk CLI
10
+ 4. **Git**: For version control
11
+
12
+ ## Step 1: Install AWS CLI and EB CLI
13
+
14
+ ### Install AWS CLI
15
+ ```bash
16
+ # macOS (using Homebrew)
17
+ brew install awscli
18
+
19
+ # Or download from AWS website
20
+ curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
21
+ sudo installer -pkg AWSCLIV2.pkg -target /
22
+ ```
23
+
24
+ ### Install EB CLI
25
+ ```bash
26
+ pip install awsebcli
27
+ ```
28
+
29
+ ### Configure AWS CLI
30
+ ```bash
31
+ aws configure
32
+ # Enter your AWS Access Key ID
33
+ # Enter your AWS Secret Access Key
34
+ # Enter your default region (e.g., us-east-1)
35
+ # Enter your output format (json)
36
+ ```
37
+
38
+ ## Step 2: Prepare Your Application
39
+
40
+ Your application is already prepared with the necessary files:
41
+ - `application.py` - Main Flask application
42
+ - `requirements.txt` - Python dependencies
43
+ - `Procfile` - Tells EB how to run the app
44
+ - `.ebextensions/01_flask.config` - EB configuration
45
+
46
+ ## Step 3: Initialize Elastic Beanstalk
47
+
48
+ ```bash
49
+ # Navigate to your project directory
50
+ cd /path/to/your/LinkedInParty
51
+
52
+ # Initialize EB application
53
+ eb init
54
+
55
+ # Follow the prompts:
56
+ # 1. Select your region
57
+ # 2. Create new application (enter app name: party-planner)
58
+ # 3. Select Python platform
59
+ # 4. Select Python version (3.11 or 3.12)
60
+ # 5. Set up SSH (optional)
61
+ ```
62
+
63
+ ## Step 4: Create Environment
64
+
65
+ ```bash
66
+ # Create and deploy your environment
67
+ eb create party-planner-env
68
+
69
+ # This will:
70
+ # 1. Create an EC2 instance
71
+ # 2. Set up load balancer
72
+ # 3. Configure security groups
73
+ # 4. Deploy your application
74
+ # 5. Provide you with a URL
75
+ ```
76
+
77
+ ## Step 5: Deploy Your Application
78
+
79
+ ```bash
80
+ # Deploy your application
81
+ eb deploy
82
+
83
+ # Or if you want to deploy and open in browser
84
+ eb deploy --open
85
+ ```
86
+
87
+ ## Step 6: Monitor Your Application
88
+
89
+ ```bash
90
+ # Check application status
91
+ eb status
92
+
93
+ # View application logs
94
+ eb logs
95
+
96
+ # Open application in browser
97
+ eb open
98
+ ```
99
+
100
+ ## Alternative Deployment Methods
101
+
102
+ ### Method 2: AWS Lambda + API Gateway
103
+
104
+ If you prefer serverless deployment:
105
+
106
+ 1. **Install AWS SAM CLI**
107
+ ```bash
108
+ # macOS
109
+ brew install aws-sam-cli
110
+ ```
111
+
112
+ 2. **Create SAM template** (create `template.yaml`):
113
+ ```yaml
114
+ AWSTemplateFormatVersion: '2010-09-09'
115
+ Transform: AWS::Serverless-2016-10-31
116
+ Resources:
117
+ PartyPlannerFunction:
118
+ Type: AWS::Serverless::Function
119
+ Properties:
120
+ CodeUri: ./
121
+ Handler: application.app
122
+ Runtime: python3.11
123
+ Events:
124
+ Api:
125
+ Type: Api
126
+ Properties:
127
+ Path: /{proxy+}
128
+ Method: ANY
129
+ ```
130
+
131
+ 3. **Deploy with SAM**
132
+ ```bash
133
+ sam build
134
+ sam deploy --guided
135
+ ```
136
+
137
+ ### Method 3: AWS ECS (Docker)
138
+
139
+ 1. **Create Dockerfile**:
140
+ ```dockerfile
141
+ FROM python:3.11-slim
142
+
143
+ WORKDIR /app
144
+ COPY requirements.txt .
145
+ RUN pip install -r requirements.txt
146
+
147
+ COPY . .
148
+
149
+ EXPOSE 5000
150
+ CMD ["gunicorn", "--bind", "0.0.0.0:5000", "application:app"]
151
+ ```
152
+
153
+ 2. **Build and push to ECR**
154
+ ```bash
155
+ aws ecr create-repository --repository-name party-planner
156
+ aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com
157
+ docker build -t party-planner .
158
+ docker tag party-planner:latest YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/party-planner:latest
159
+ docker push YOUR_ACCOUNT_ID.dkr.ecr.us-east-1.amazonaws.com/party-planner:latest
160
+ ```
161
+
162
+ ## Environment Variables (Optional)
163
+
164
+ You can set environment variables in the EB console or via CLI:
165
+
166
+ ```bash
167
+ eb setenv SECRET_KEY=your-secret-key-here
168
+ ```
169
+
170
+ ## Custom Domain (Optional)
171
+
172
+ 1. **Register domain in Route 53** or use existing domain
173
+ 2. **Create SSL certificate** in AWS Certificate Manager
174
+ 3. **Configure domain** in EB console or via CLI
175
+
176
+ ```bash
177
+ eb config
178
+ # Add domain configuration
179
+ ```
180
+
181
+ ## Monitoring and Scaling
182
+
183
+ ### Auto Scaling
184
+ ```bash
185
+ # Configure auto scaling
186
+ eb config
187
+ # Set min/max instances based on your needs
188
+ ```
189
+
190
+ ### CloudWatch Monitoring
191
+ - Set up CloudWatch alarms
192
+ - Monitor application metrics
193
+ - Set up log aggregation
194
+
195
+ ## Cost Optimization
196
+
197
+ ### Free Tier
198
+ - AWS offers 12 months free tier
199
+ - 750 hours/month of t2.micro instances
200
+ - 20GB of storage
201
+
202
+ ### Cost Saving Tips
203
+ 1. Use t3.micro instances for development
204
+ 2. Set up auto scaling to scale down during low usage
205
+ 3. Use Spot Instances for non-critical workloads
206
+ 4. Monitor costs in AWS Cost Explorer
207
+
208
+ ## Troubleshooting
209
+
210
+ ### Common Issues
211
+
212
+ 1. **Deployment Fails**
213
+ ```bash
214
+ eb logs
215
+ # Check for specific error messages
216
+ ```
217
+
218
+ 2. **Application Not Responding**
219
+ ```bash
220
+ eb health
221
+ # Check application health
222
+ ```
223
+
224
+ 3. **Environment Issues**
225
+ ```bash
226
+ eb events
227
+ # View recent events
228
+ ```
229
+
230
+ ### Useful Commands
231
+
232
+ ```bash
233
+ # SSH into your instance
234
+ eb ssh
235
+
236
+ # View environment info
237
+ eb info
238
+
239
+ # List all environments
240
+ eb list
241
+
242
+ # Terminate environment
243
+ eb terminate party-planner-env
244
+
245
+ # Clone environment
246
+ eb clone party-planner-env --clone_name party-planner-env-2
247
+ ```
248
+
249
+ ## Security Best Practices
250
+
251
+ 1. **Use IAM roles** instead of access keys
252
+ 2. **Enable VPC** for network isolation
253
+ 3. **Set up security groups** to restrict access
254
+ 4. **Use HTTPS** for all communications
255
+ 5. **Regular security updates**
256
+
257
+ ## Backup and Recovery
258
+
259
+ 1. **Database backups** (if using RDS)
260
+ 2. **Application backups** (version control)
261
+ 3. **Configuration backups** (EB configuration)
262
+ 4. **Disaster recovery plan**
263
+
264
+ ## Performance Optimization
265
+
266
+ 1. **Enable CloudFront** for static content
267
+ 2. **Use RDS** for database (if needed)
268
+ 3. **Implement caching** (Redis/ElastiCache)
269
+ 4. **Optimize application code**
270
+
271
+ ## Maintenance
272
+
273
+ ### Regular Tasks
274
+ 1. **Update dependencies** monthly
275
+ 2. **Monitor costs** weekly
276
+ 3. **Review logs** for issues
277
+ 4. **Update SSL certificates** before expiry
278
+
279
+ ### Updates
280
+ ```bash
281
+ # Update application
282
+ git add .
283
+ git commit -m "Update application"
284
+ eb deploy
285
+
286
+ # Update environment
287
+ eb upgrade
288
+ ```
289
+
290
+ ## Support
291
+
292
+ - **AWS Documentation**: https://docs.aws.amazon.com/elasticbeanstalk/
293
+ - **AWS Support**: Available with paid plans
294
+ - **Community Forums**: AWS Developer Forums
295
+ - **Stack Overflow**: Tag with aws-elasticbeanstalk
296
+
297
+ ## Estimated Costs
298
+
299
+ ### Small Application (t3.micro)
300
+ - **EC2**: ~$8-15/month
301
+ - **Load Balancer**: ~$18/month
302
+ - **Storage**: ~$1-5/month
303
+ - **Data Transfer**: ~$1-10/month
304
+ - **Total**: ~$30-50/month
305
+
306
+ ### Free Tier
307
+ - **First 12 months**: Free (with limitations)
308
+ - **After free tier**: ~$30-50/month
309
+
310
+ ## Next Steps
311
+
312
+ 1. **Set up monitoring** with CloudWatch
313
+ 2. **Configure custom domain**
314
+ 3. **Set up CI/CD pipeline**
315
+ 4. **Implement backup strategy**
316
+ 5. **Plan for scaling**
317
+
318
+ Your Party Planner app is now ready for production deployment on AWS! 🚀
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: gunicorn application:app
QUICKSTART.md ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quick Start Guide
2
+
3
+ ## 🚀 Get Started in 3 Steps
4
+
5
+ ### 1. Install Dependencies
6
+ ```bash
7
+ pip3 install -r requirements.txt
8
+ ```
9
+
10
+ ### 2. Run the App
11
+ ```bash
12
+ python3 run.py
13
+ ```
14
+
15
+ ### 3. Open Your Browser
16
+ Navigate to: http://localhost:5000
17
+
18
+ ## 🎯 What You'll See
19
+
20
+ 1. **Login Page**: Enter your LinkedIn credentials
21
+ 2. **Connections List**: View up to 100 of your LinkedIn connections
22
+ 3. **Selection Interface**: Click to select connections (max 30)
23
+ 4. **Table Arrangement**: Automatically organize into 3 tables of 10
24
+
25
+ ## ⚡ Features at a Glance
26
+
27
+ - ✅ **Smart Categorization**: Groups connections by industry/role
28
+ - ✅ **Balanced Tables**: Ensures diverse table compositions
29
+ - ✅ **Interactive UI**: Modern, responsive design
30
+ - ✅ **Real-time Stats**: Track your selections
31
+
32
+ ## 🔧 Troubleshooting
33
+
34
+ **Chrome not found?**
35
+ - Install Google Chrome browser
36
+ - Make sure it's in your PATH
37
+
38
+ **Login issues?**
39
+ - Verify your LinkedIn credentials
40
+ - Check if LinkedIn requires 2FA
41
+
42
+ **Connection loading slow?**
43
+ - This is normal - LinkedIn loads connections gradually
44
+ - Keep the browser window visible
45
+
46
+ ## 📱 Mobile Friendly
47
+
48
+ The app works great on mobile devices too!
49
+
50
+ ---
51
+
52
+ **Need help?** Check the full README.md for detailed instructions.
README.md CHANGED
@@ -1,14 +1,182 @@
1
- ---
2
- title: PartySim
3
- emoji:
4
- colorFrom: indigo
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.34.2
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: Upload your guest list and watch the party happen
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Party Planner - Guest Table Arranger
2
+
3
+ A web application that helps you organize your guest list into tables for networking events. The app accepts CSV file uploads with guest information and creates visual circular table arrangements showing where each person will be seated.
4
+
5
+ ## Features
6
+
7
+ - 📁 Easy CSV file upload with drag & drop support
8
+ - 📋 Parse guest information from name and message columns
9
+ - ✅ Interactive guest selection with checkboxes
10
+ - 🎯 Smart table arrangement algorithm
11
+ - 🎨 Visual circular table layouts with seating positions
12
+ - 📱 Responsive design for mobile and desktop
13
+ - 🎨 Modern, intuitive UI
14
+
15
+ ## Prerequisites
16
+
17
+ - Python 3.7 or higher
18
+ - Web browser
19
+
20
+ ## Installation
21
+
22
+ 1. **Clone or download this repository**
23
+ ```bash
24
+ git clone <repository-url>
25
+ cd LinkedInParty
26
+ ```
27
+
28
+ 2. **Install Python dependencies**
29
+ ```bash
30
+ pip install -r requirements.txt
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ 1. **Start the application**
36
+ ```bash
37
+ python3 app.py
38
+ ```
39
+
40
+ 2. **Open your browser**
41
+ Navigate to `http://localhost:5000`
42
+
43
+ 3. **Prepare your CSV file**
44
+ Your CSV should have exactly 2 columns:
45
+ - **First column**: guest names
46
+ - **Second column**: descriptions, roles, or messages
47
+
48
+ 4. **Upload your guest list**
49
+ - Click "Upload Guest List" or drag and drop your CSV file
50
+ - The app will parse and display up to 100 guests
51
+
52
+ 5. **Select guests for your event**
53
+ - Click on guest cards to select/deselect them
54
+ - You can select up to 30 guests (3 tables × 10 people)
55
+ - The stats panel shows your selection progress
56
+
57
+ 6. **Arrange tables**
58
+ - Click "Arrange Tables" to automatically organize your selected guests
59
+ - The app will create three visual circular tables
60
+ - Each table shows seating positions with guest initials
61
+ - Hover over seats to see full names and descriptions
62
+
63
+ ## CSV Format Example
64
+
65
+ ```csv
66
+ name,message
67
+ John Smith,Software Engineer at TechCorp
68
+ Sarah Johnson,Marketing Director at Creative Agency
69
+ Michael Brown,CEO of StartupXYZ
70
+ Emily Davis,Data Scientist at AI Labs
71
+ David Wilson,Product Manager at Innovation Inc
72
+ ```
73
+
74
+ ## Visual Table Layout
75
+
76
+ The app creates beautiful circular table visualizations:
77
+ - 🟢 **Green circles** represent individual seats
78
+ - 🔵 **Blue center** shows the table number
79
+ - 👤 **Initials** displayed on each seat
80
+ - 💬 **Hover tooltips** show full names and descriptions
81
+ - 🎯 **Smart positioning** arranges seats evenly around the table
82
+
83
+ ## How it Works
84
+
85
+ ### CSV Processing
86
+ - Supports multiple CSV delimiters (comma, semicolon, tab)
87
+ - Automatically detects column headers
88
+ - Reads first column for names and second column for descriptions
89
+ - Handles various encoding formats
90
+
91
+ ### Table Arrangement Algorithm
92
+ The app uses a smart distribution algorithm:
93
+ 1. Categorizes guests by industry/role type based on message content
94
+ 2. Distributes major categories evenly across 3 tables
95
+ 3. Ensures each table has a maximum of 10 people
96
+ 4. Balances the number of people per table
97
+
98
+ ### Smart Categorization
99
+ Based on message content, guests are categorized as:
100
+ - **Tech**: engineer, developer, programmer, software, tech, it, data, ai, ml, technology, scientist
101
+ - **Business**: manager, director, ceo, founder, executive, business, strategy, operations, consultant, product
102
+ - **Creative**: designer, creative, marketing, content, writer, artist, media, communications, strategist
103
+ - **Sales**: sales, account, client, business development, partnership, account manager
104
+ - **Finance**: finance, accounting, investment, banking, financial, analyst, cfo
105
+ - **Other**: any roles not matching the above categories
106
+
107
+ ### Visual Seating Layout
108
+ - **Circular tables** with realistic seating arrangements
109
+ - **Even distribution** of seats around each table
110
+ - **Interactive elements** with hover effects
111
+ - **Responsive design** that works on all devices
112
+
113
+ ## Troubleshooting
114
+
115
+ ### Common Issues
116
+
117
+ 1. **CSV Format Problems**
118
+ - Ensure your CSV has exactly 2 columns (name and message)
119
+ - Check that the first column contains guest names
120
+ - Verify the second column contains role descriptions
121
+
122
+ 2. **File Upload Issues**
123
+ - Make sure you're uploading a .csv file
124
+ - Check that the file isn't corrupted or empty
125
+ - Try a smaller file first to test the format
126
+
127
+ 3. **Parsing Errors**
128
+ - The app will try different delimiters automatically
129
+ - If parsing fails, check your CSV format in a text editor
130
+ - Ensure there are no special characters causing issues
131
+
132
+ ### Performance Tips
133
+
134
+ - The app can handle up to 100 guests efficiently
135
+ - Larger files may take a moment to process
136
+ - Keep your CSV file under 1MB for best performance
137
+
138
+ ## Technical Details
139
+
140
+ ### Backend (Flask)
141
+ - `app.py`: Main Flask application with CSV processing
142
+ - Uses Python's built-in CSV module for parsing
143
+ - RESTful API endpoints for frontend communication
144
+
145
+ ### Frontend (HTML/CSS/JavaScript)
146
+ - Modern responsive design
147
+ - Drag & drop file upload
148
+ - Interactive guest selection
149
+ - Circular table visualization with CSS positioning
150
+ - Real-time statistics updates
151
+
152
+ ### Dependencies
153
+ - `flask`: Web framework
154
+ - `flask-cors`: Cross-origin resource sharing
155
+ - `werkzeug`: File upload handling
156
+
157
+ ## Limitations
158
+
159
+ - Maximum 30 guests can be arranged (3 tables × 10 people)
160
+ - Maximum 100 guests can be loaded from CSV
161
+ - CSV files only (no Excel or other formats)
162
+ - Guest data is not persistent between sessions
163
+
164
+ ## Future Enhancements
165
+
166
+ - [ ] Support for Excel files (.xlsx, .xls)
167
+ - [ ] Save/load table arrangements
168
+ - [ ] Export to CSV/PDF
169
+ - [ ] Custom table sizes
170
+ - [ ] Guest filtering by industry/company
171
+ - [ ] Integration with calendar systems
172
+ - [ ] Email invitations to guests
173
+ - [ ] 3D table visualization
174
+ - [ ] Drag & drop seating arrangements
175
+
176
+ ## Contributing
177
+
178
+ Feel free to submit issues and enhancement requests!
179
+
180
+ ## License
181
+
182
+ This project is for educational and personal use purposes.
README_HF.md ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎉 Party Planner - Guest Table Arranger
2
+
3
+ A smart web application that helps you organize your guest list into optimal table arrangements for networking events and parties. Built with Gradio and deployed on Hugging Face Spaces.
4
+
5
+ ## ✨ Features
6
+
7
+ - 📁 **Easy CSV Upload**: Simply paste your CSV content or upload a file
8
+ - 🧠 **Smart Table Arrangement**: AI-powered algorithm that categorizes guests by industry/role
9
+ - 🎯 **Optimal Distribution**: Automatically creates tables of 10 with balanced industry mix
10
+ - 📊 **Visual Results**: Clear, formatted output showing table assignments
11
+ - 🎨 **Modern Interface**: Beautiful, responsive Gradio interface
12
+ - 📱 **Mobile Friendly**: Works perfectly on all devices
13
+
14
+ ## 🚀 How to Use
15
+
16
+ 1. **Prepare Your CSV**: Create a CSV file with exactly 2 columns:
17
+ - **First column**: Guest names
18
+ - **Second column**: Job titles, roles, or descriptions
19
+
20
+ 2. **Upload Your Data**:
21
+ - Paste your CSV content directly into the text box, OR
22
+ - Upload your CSV file using the file upload button
23
+
24
+ 3. **Arrange Tables**: Click the "🎯 Arrange Tables" button to see the smart arrangement
25
+
26
+ 4. **View Results**: See your guests organized into optimal table seating with industry-balanced distribution
27
+
28
+ ## 📄 CSV Format Example
29
+
30
+ ```csv
31
+ name,message
32
+ John Smith,Software Engineer at TechCorp
33
+ Sarah Johnson,Marketing Director at Creative Agency
34
+ Michael Brown,CEO of StartupXYZ
35
+ Emily Davis,Data Scientist at AI Labs
36
+ David Wilson,Product Manager at Innovation Inc
37
+ ```
38
+
39
+ ## 🧠 Smart Categorization
40
+
41
+ The app automatically categorizes guests based on their job descriptions:
42
+
43
+ - **Tech**: engineer, developer, programmer, software, tech, data, AI, ML, scientist
44
+ - **Business**: manager, director, CEO, founder, executive, strategy, operations, consultant
45
+ - **Creative**: designer, creative, marketing, content, writer, artist, media
46
+ - **Sales**: sales, account, client, business development, partnership
47
+ - **Finance**: finance, accounting, investment, banking, analyst, CFO
48
+ - **Other**: any roles not matching the above categories
49
+
50
+ ## 🎯 Table Arrangement Algorithm
51
+
52
+ 1. **Categorizes** guests by industry/role type
53
+ 2. **Distributes** major categories evenly across tables
54
+ 3. **Balances** table sizes (maximum 10 guests per table)
55
+ 4. **Creates** unlimited tables as needed
56
+ 5. **Ensures** diverse networking opportunities
57
+
58
+ ## 📊 Sample Output
59
+
60
+ ```
61
+ 🎉 Successfully processed 25 guests!
62
+
63
+ 📊 Created 3 table(s) with smart distribution:
64
+
65
+ 🍽️ **Table 1** (9 guests):
66
+ 1. **John Smith** - Software Engineer at TechCorp
67
+ 2. **Sarah Johnson** - Marketing Director at Creative Agency
68
+ 3. **Michael Brown** - CEO of StartupXYZ
69
+ ...
70
+
71
+ 🍽️ **Table 2** (8 guests):
72
+ 1. **Emily Davis** - Data Scientist at AI Labs
73
+ 2. **David Wilson** - Product Manager at Innovation Inc
74
+ ...
75
+
76
+ 🍽️ **Table 3** (8 guests):
77
+ 1. **Lisa Chen** - UX Designer at Design Studio
78
+ ...
79
+
80
+ 📈 **Table Statistics:**
81
+ Table 1: 9 guests
82
+ Table 2: 8 guests
83
+ Table 3: 8 guests
84
+ ```
85
+
86
+ ## 🛠️ Technical Details
87
+
88
+ - **Framework**: Gradio (Python)
89
+ - **Algorithm**: Smart categorization and distribution
90
+ - **Deployment**: Hugging Face Spaces
91
+ - **Dependencies**: gradio>=4.0.0
92
+
93
+ ## 🎨 Features
94
+
95
+ - **Drag & Drop**: Easy file upload
96
+ - **Real-time Processing**: Instant table arrangement
97
+ - **Error Handling**: Clear error messages for invalid formats
98
+ - **Sample Data**: Built-in sample CSV for testing
99
+ - **Responsive Design**: Works on desktop and mobile
100
+
101
+ ## 🔧 Local Development
102
+
103
+ To run this locally:
104
+
105
+ ```bash
106
+ pip install gradio
107
+ python app.py
108
+ ```
109
+
110
+ ## 📝 License
111
+
112
+ This project is for educational and personal use purposes.
113
+
114
+ ---
115
+
116
+ **Made with ❤️ for better networking events!**
app.py ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face Spaces Entry Point
2
+ # This file launches the Gradio interface for the Party Planner app
3
+
4
+ from app_gradio import demo
5
+
6
+ # Launch the Gradio app
7
+ if __name__ == "__main__":
8
+ demo.launch()
app_gradio.py ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import csv
3
+ import io
4
+ import json
5
+ from typing import List, Dict, Any
6
+
7
+ def parse_csv_file(file_content: str) -> Dict[str, Any]:
8
+ """Parse CSV content and extract guest information from name and message columns"""
9
+ try:
10
+ # Create a StringIO object to read the CSV
11
+ csv_file = io.StringIO(file_content)
12
+
13
+ # Read CSV with different possible delimiters
14
+ for delimiter in [',', ';', '\t']:
15
+ try:
16
+ csv_file.seek(0) # Reset file pointer
17
+ reader = csv.DictReader(csv_file, delimiter=delimiter)
18
+
19
+ # Get the header row
20
+ headers = reader.fieldnames
21
+ if not headers or len(headers) < 2:
22
+ continue
23
+
24
+ # Use the first two columns: name and message
25
+ name_column = headers[0]
26
+ message_column = headers[1]
27
+
28
+ # Parse the data and filter out entries with blank names or descriptions
29
+ guests = []
30
+ for i, row in enumerate(reader):
31
+ name = row.get(name_column, '').strip()
32
+ title = row.get(message_column, '').strip()
33
+
34
+ # Only add if both name and description are not blank
35
+ if name and title:
36
+ guests.append({
37
+ 'id': i,
38
+ 'name': name,
39
+ 'title': title
40
+ })
41
+
42
+ if guests:
43
+ return {'success': True, 'guests': guests, 'total': len(guests)}
44
+
45
+ except Exception as e:
46
+ continue
47
+
48
+ return {'success': False, 'error': 'Could not parse CSV file. Please ensure it has at least 2 columns (name and message).'}
49
+
50
+ except Exception as e:
51
+ return {'success': False, 'error': f'Error parsing CSV: {str(e)}'}
52
+
53
+ def arrange_guests_into_tables(guests: List[Dict]) -> List[List[Dict]]:
54
+ """Arrange guests into tables of 10 people each with smart distribution"""
55
+ if not guests:
56
+ return []
57
+
58
+ # Categorize guests by industry/role type based on message content
59
+ categories = {
60
+ 'tech': ['engineer', 'developer', 'programmer', 'software', 'tech', 'it', 'data', 'ai', 'ml', 'technology', 'scientist'],
61
+ 'business': ['manager', 'director', 'ceo', 'founder', 'executive', 'business', 'strategy', 'operations', 'consultant', 'product'],
62
+ 'creative': ['designer', 'creative', 'marketing', 'content', 'writer', 'artist', 'media', 'communications', 'strategist'],
63
+ 'sales': ['sales', 'account', 'client', 'business development', 'partnership', 'account manager'],
64
+ 'finance': ['finance', 'accounting', 'investment', 'banking', 'financial', 'analyst', 'cfo'],
65
+ 'other': []
66
+ }
67
+
68
+ categorized_guests = {cat: [] for cat in categories.keys()}
69
+
70
+ # Categorize each guest based on their message/description
71
+ for guest in guests:
72
+ title_lower = guest['title'].lower()
73
+ categorized = False
74
+
75
+ for category, keywords in categories.items():
76
+ if category == 'other':
77
+ continue
78
+ if any(keyword in title_lower for keyword in keywords):
79
+ categorized_guests[category].append(guest)
80
+ categorized = True
81
+ break
82
+
83
+ if not categorized:
84
+ categorized_guests['other'].append(guest)
85
+
86
+ # Calculate how many tables we need
87
+ total_guests = len(guests)
88
+ num_tables = (total_guests + 9) // 10 # Ceiling division to get number of tables needed
89
+
90
+ # Initialize tables
91
+ tables = [[] for _ in range(num_tables)]
92
+
93
+ # Distribute guests strategically across all tables
94
+ # First, distribute major categories evenly
95
+ major_categories = ['tech', 'business', 'creative', 'sales']
96
+
97
+ for category in major_categories:
98
+ guests_in_category = categorized_guests[category]
99
+ if guests_in_category:
100
+ # Distribute evenly across all tables
101
+ for i, guest in enumerate(guests_in_category):
102
+ table_index = i % num_tables
103
+ if len(tables[table_index]) < 10:
104
+ tables[table_index].append(guest)
105
+
106
+ # Then distribute remaining guests (finance, other)
107
+ remaining_guests = []
108
+ for category in ['finance', 'other']:
109
+ remaining_guests.extend(categorized_guests[category])
110
+
111
+ # Also add any guests that didn't fit in the first round
112
+ for table in tables:
113
+ for guest in guests:
114
+ if guest not in [g for table_guests in tables for g in table_guests]:
115
+ if len(table) < 10:
116
+ table.append(guest)
117
+ break
118
+
119
+ # Fill remaining slots with any leftover guests
120
+ for guest in guests:
121
+ if guest not in [g for table_guests in tables for g in table_guests]:
122
+ for table in tables:
123
+ if len(table) < 10:
124
+ table.append(guest)
125
+ break
126
+
127
+ return tables
128
+
129
+ def process_csv_and_arrange_tables(csv_content: str) -> str:
130
+ """Main function to process CSV and arrange tables"""
131
+ try:
132
+ # Parse CSV
133
+ result = parse_csv_file(csv_content)
134
+
135
+ if not result['success']:
136
+ return f"❌ Error: {result['error']}"
137
+
138
+ guests = result['guests']
139
+ total_guests = result['total']
140
+
141
+ if total_guests == 0:
142
+ return "❌ No valid guests found in CSV. Please ensure you have at least 2 columns (name and message) with non-empty values."
143
+
144
+ # Arrange tables
145
+ tables = arrange_guests_into_tables(guests)
146
+
147
+ if not tables:
148
+ return "❌ No tables could be created."
149
+
150
+ # Generate output
151
+ output = f"🎉 Successfully processed {total_guests} guests!\n\n"
152
+ output += f"📊 Created {len(tables)} table(s) with smart distribution:\n\n"
153
+
154
+ for i, table in enumerate(tables, 1):
155
+ output += f"🍽️ **Table {i}** ({len(table)} guests):\n"
156
+ for j, guest in enumerate(table, 1):
157
+ output += f" {j}. **{guest['name']}** - {guest['title']}\n"
158
+ output += "\n"
159
+
160
+ # Add statistics
161
+ output += "📈 **Table Statistics:**\n"
162
+ for i, table in enumerate(tables, 1):
163
+ output += f" Table {i}: {len(table)} guests\n"
164
+
165
+ return output
166
+
167
+ except Exception as e:
168
+ return f"❌ Error processing request: {str(e)}"
169
+
170
+ def create_sample_csv() -> str:
171
+ """Create a sample CSV for users to download"""
172
+ sample_data = """name,message
173
+ John Smith,Software Engineer at TechCorp
174
+ Sarah Johnson,Marketing Director at Creative Agency
175
+ Michael Brown,CEO of StartupXYZ
176
+ Emily Davis,Data Scientist at AI Labs
177
+ David Wilson,Product Manager at Innovation Inc
178
+ Lisa Chen,UX Designer at Design Studio
179
+ Robert Taylor,Sales Manager at SalesForce
180
+ Amanda Rodriguez,Financial Analyst at Finance Corp
181
+ James Lee,Content Strategist at Media Group
182
+ Jennifer White,Business Development at Growth Co"""
183
+ return sample_data
184
+
185
+ # Create the Gradio interface
186
+ with gr.Blocks(
187
+ title="Party Planner - Guest Table Arranger",
188
+ theme=gr.themes.Soft(),
189
+ css="""
190
+ .gradio-container {
191
+ max-width: 1200px !important;
192
+ margin: 0 auto !important;
193
+ }
194
+ .main-header {
195
+ text-align: center;
196
+ margin-bottom: 2rem;
197
+ }
198
+ .sample-csv {
199
+ background: #f0f8ff;
200
+ padding: 1rem;
201
+ border-radius: 8px;
202
+ margin: 1rem 0;
203
+ }
204
+ """
205
+ ) as demo:
206
+
207
+ gr.HTML("""
208
+ <div class="main-header">
209
+ <h1>🎉 Party Planner - Guest Table Arranger</h1>
210
+ <p>Upload your guest list CSV and let AI arrange them into optimal table seating!</p>
211
+ </div>
212
+ """)
213
+
214
+ with gr.Row():
215
+ with gr.Column(scale=1):
216
+ gr.HTML("""
217
+ <h3>📋 How to use:</h3>
218
+ <ol>
219
+ <li>Prepare a CSV file with 2 columns: <strong>name</strong> and <strong>message/description</strong></li>
220
+ <li>Upload your CSV file below</li>
221
+ <li>Click "Arrange Tables" to see the smart seating arrangement</li>
222
+ </ol>
223
+
224
+ <div class="sample-csv">
225
+ <h4>📄 Sample CSV Format:</h4>
226
+ <pre>name,message
227
+ John Smith,Software Engineer at TechCorp
228
+ Sarah Johnson,Marketing Director at Creative Agency
229
+ Michael Brown,CEO of StartupXYZ</pre>
230
+ </div>
231
+ """)
232
+
233
+ sample_csv = gr.Textbox(
234
+ label="Sample CSV Content",
235
+ value=create_sample_csv(),
236
+ lines=10,
237
+ interactive=False
238
+ )
239
+
240
+ download_sample = gr.Button("📥 Download Sample CSV", variant="secondary")
241
+
242
+ with gr.Column(scale=2):
243
+ csv_input = gr.Textbox(
244
+ label="📁 Paste your CSV content here (or upload file below)",
245
+ placeholder="Paste your CSV content here...",
246
+ lines=10
247
+ )
248
+
249
+ file_input = gr.File(
250
+ label="📂 Or upload CSV file",
251
+ file_types=[".csv"],
252
+ file_count="single"
253
+ )
254
+
255
+ arrange_btn = gr.Button("🎯 Arrange Tables", variant="primary", size="lg")
256
+
257
+ output = gr.Markdown(
258
+ label="📊 Table Arrangement Results",
259
+ value="Upload your guest list to see the table arrangement!"
260
+ )
261
+
262
+ # Event handlers
263
+ def handle_file_upload(file):
264
+ if file is None:
265
+ return ""
266
+ try:
267
+ with open(file.name, 'r', encoding='utf-8') as f:
268
+ content = f.read()
269
+ return content
270
+ except Exception as e:
271
+ return f"Error reading file: {str(e)}"
272
+
273
+ def download_sample_csv():
274
+ return create_sample_csv()
275
+
276
+ # Connect events
277
+ file_input.change(
278
+ fn=handle_file_upload,
279
+ inputs=[file_input],
280
+ outputs=[csv_input]
281
+ )
282
+
283
+ arrange_btn.click(
284
+ fn=process_csv_and_arrange_tables,
285
+ inputs=[csv_input],
286
+ outputs=[output]
287
+ )
288
+
289
+ download_sample.click(
290
+ fn=download_sample_csv,
291
+ outputs=[csv_input]
292
+ )
293
+
294
+ # Launch the app
295
+ if __name__ == "__main__":
296
+ demo.launch()
application.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from flask_cors import CORS
3
+ import csv
4
+ import io
5
+ import os
6
+ from werkzeug.utils import secure_filename
7
+
8
+ app = Flask(__name__)
9
+ CORS(app)
10
+
11
+ # Configure upload settings
12
+ UPLOAD_FOLDER = '/tmp/uploads' # Use /tmp for AWS Lambda compatibility
13
+ ALLOWED_EXTENSIONS = {'csv'}
14
+
15
+ if not os.path.exists(UPLOAD_FOLDER):
16
+ os.makedirs(UPLOAD_FOLDER)
17
+
18
+ app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
19
+
20
+ def allowed_file(filename):
21
+ return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
22
+
23
+ def parse_csv_file(file_content):
24
+ """Parse CSV content and extract guest information from name and message columns"""
25
+ try:
26
+ # Decode the file content
27
+ if isinstance(file_content, bytes):
28
+ file_content = file_content.decode('utf-8')
29
+
30
+ # Create a StringIO object to read the CSV
31
+ csv_file = io.StringIO(file_content)
32
+
33
+ # Read CSV with different possible delimiters
34
+ for delimiter in [',', ';', '\t']:
35
+ try:
36
+ csv_file.seek(0) # Reset file pointer
37
+ reader = csv.DictReader(csv_file, delimiter=delimiter)
38
+
39
+ # Get the header row
40
+ headers = reader.fieldnames
41
+ if not headers or len(headers) < 2:
42
+ continue
43
+
44
+ # Use the first two columns: name and message
45
+ name_column = headers[0]
46
+ message_column = headers[1]
47
+
48
+ # Parse the data and filter out entries with blank names or descriptions
49
+ guests = []
50
+ for i, row in enumerate(reader):
51
+ name = row.get(name_column, '').strip()
52
+ title = row.get(message_column, '').strip()
53
+
54
+ # Only add if both name and description are not blank
55
+ if name and title:
56
+ guests.append({
57
+ 'id': i,
58
+ 'name': name,
59
+ 'title': title
60
+ })
61
+
62
+ if guests:
63
+ return {'success': True, 'guests': guests, 'total': len(guests)}
64
+
65
+ except Exception as e:
66
+ continue
67
+
68
+ return {'success': False, 'error': 'Could not parse CSV file. Please ensure it has at least 2 columns (name and message).'}
69
+
70
+ except Exception as e:
71
+ return {'success': False, 'error': f'Error parsing CSV: {str(e)}'}
72
+
73
+ @app.route('/')
74
+ def index():
75
+ return render_template('index.html')
76
+
77
+ @app.route('/upload-csv', methods=['POST'])
78
+ def upload_csv():
79
+ """Handle CSV file upload"""
80
+ try:
81
+ if 'file' not in request.files:
82
+ return jsonify({'success': False, 'error': 'No file uploaded'})
83
+
84
+ file = request.files['file']
85
+
86
+ if file.filename == '':
87
+ return jsonify({'success': False, 'error': 'No file selected'})
88
+
89
+ if not allowed_file(file.filename):
90
+ return jsonify({'success': False, 'error': 'Please upload a CSV file'})
91
+
92
+ # Read file content
93
+ file_content = file.read()
94
+
95
+ # Parse CSV
96
+ result = parse_csv_file(file_content)
97
+
98
+ if result['success']:
99
+ return jsonify({
100
+ 'success': True,
101
+ 'guests': result['guests'],
102
+ 'total': result['total'],
103
+ 'message': f'Successfully loaded {result["total"]} guests from CSV (filtered out entries with blank names or descriptions)'
104
+ })
105
+ else:
106
+ return jsonify(result)
107
+
108
+ except Exception as e:
109
+ return jsonify({'success': False, 'error': f'Upload error: {str(e)}'})
110
+
111
+ @app.route('/arrange-tables', methods=['POST'])
112
+ def arrange_tables():
113
+ data = request.get_json()
114
+ selected_guests = data.get('selected_guests', [])
115
+
116
+ if not selected_guests:
117
+ return jsonify({'success': False, 'error': 'No guests selected for arrangement'})
118
+
119
+ # Smart arrangement algorithm - create unlimited tables of 10
120
+ tables = arrange_guests_into_tables(selected_guests)
121
+
122
+ return jsonify({'success': True, 'tables': tables})
123
+
124
+ def arrange_guests_into_tables(guests):
125
+ """Arrange guests into tables of 10 people each with smart distribution"""
126
+ if not guests:
127
+ return []
128
+
129
+ # Categorize guests by industry/role type based on message content
130
+ categories = {
131
+ 'tech': ['engineer', 'developer', 'programmer', 'software', 'tech', 'it', 'data', 'ai', 'ml', 'technology', 'scientist'],
132
+ 'business': ['manager', 'director', 'ceo', 'founder', 'executive', 'business', 'strategy', 'operations', 'consultant', 'product'],
133
+ 'creative': ['designer', 'creative', 'marketing', 'content', 'writer', 'artist', 'media', 'communications', 'strategist'],
134
+ 'sales': ['sales', 'account', 'client', 'business development', 'partnership', 'account manager'],
135
+ 'finance': ['finance', 'accounting', 'investment', 'banking', 'financial', 'analyst', 'cfo'],
136
+ 'other': []
137
+ }
138
+
139
+ categorized_guests = {cat: [] for cat in categories.keys()}
140
+
141
+ # Categorize each guest based on their message/description
142
+ for guest in guests:
143
+ title_lower = guest['title'].lower()
144
+ categorized = False
145
+
146
+ for category, keywords in categories.items():
147
+ if category == 'other':
148
+ continue
149
+ if any(keyword in title_lower for keyword in keywords):
150
+ categorized_guests[category].append(guest)
151
+ categorized = True
152
+ break
153
+
154
+ if not categorized:
155
+ categorized_guests['other'].append(guest)
156
+
157
+ # Calculate how many tables we need
158
+ total_guests = len(guests)
159
+ num_tables = (total_guests + 9) // 10 # Ceiling division to get number of tables needed
160
+
161
+ # Initialize tables
162
+ tables = [[] for _ in range(num_tables)]
163
+
164
+ # Distribute guests strategically across all tables
165
+ # First, distribute major categories evenly
166
+ major_categories = ['tech', 'business', 'creative', 'sales']
167
+
168
+ for category in major_categories:
169
+ guests_in_category = categorized_guests[category]
170
+ if guests_in_category:
171
+ # Distribute evenly across all tables
172
+ for i, guest in enumerate(guests_in_category):
173
+ table_index = i % num_tables
174
+ if len(tables[table_index]) < 10:
175
+ tables[table_index].append(guest)
176
+
177
+ # Then distribute remaining guests (finance, other)
178
+ remaining_guests = []
179
+ for category in ['finance', 'other']:
180
+ remaining_guests.extend(categorized_guests[category])
181
+
182
+ # Also add any guests that didn't fit in the first round
183
+ for table in tables:
184
+ for guest in guests:
185
+ if guest not in [g for table_guests in tables for g in table_guests]:
186
+ if len(table) < 10:
187
+ table.append(guest)
188
+ break
189
+
190
+ # Fill remaining slots with any leftover guests
191
+ for guest in guests:
192
+ if guest not in [g for table_guests in tables for g in table_guests]:
193
+ for table in tables:
194
+ if len(table) < 10:
195
+ table.append(guest)
196
+ break
197
+
198
+ return tables
199
+
200
+ if __name__ == '__main__':
201
+ app.run(host='0.0.0.0', port=5000)
codecommit-policy.json ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "Version": "2012-10-17",
3
+ "Statement": [
4
+ {
5
+ "Effect": "Allow",
6
+ "Action": [
7
+ "codecommit:ListRepositories",
8
+ "codecommit:GetRepository",
9
+ "codecommit:CreateRepository",
10
+ "codecommit:DeleteRepository",
11
+ "codecommit:GetBranch",
12
+ "codecommit:CreateBranch",
13
+ "codecommit:DeleteBranch",
14
+ "codecommit:ListBranches",
15
+ "codecommit:GetCommit",
16
+ "codecommit:GetDifferences",
17
+ "codecommit:GetFile",
18
+ "codecommit:GetFolder",
19
+ "codecommit:PutFile",
20
+ "codecommit:CreateCommit",
21
+ "codecommit:BatchGetRepositories",
22
+ "codecommit:GetBlob",
23
+ "codecommit:GetTree",
24
+ "codecommit:GetCommitHistory",
25
+ "codecommit:GetPullRequest",
26
+ "codecommit:ListPullRequests",
27
+ "codecommit:CreatePullRequest",
28
+ "codecommit:MergePullRequestByFastForward",
29
+ "codecommit:PostCommentForPullRequest",
30
+ "codecommit:GetCommentsForPullRequest",
31
+ "codecommit:GetCommentsForComparedCommit",
32
+ "codecommit:PostCommentReply",
33
+ "codecommit:GetComment",
34
+ "codecommit:UpdateComment",
35
+ "codecommit:DeleteComment",
36
+ "codecommit:GetMergeConflicts",
37
+ "codecommit:DescribeMergeConflicts",
38
+ "codecommit:GetMergeOptions",
39
+ "codecommit:BatchDescribeMergeConflicts",
40
+ "codecommit:EvaluatePullRequestApprovalRules",
41
+ "codecommit:OverridePullRequestApprovalRules",
42
+ "codecommit:GetApprovalRuleTemplate",
43
+ "codecommit:ListApprovalRuleTemplates",
44
+ "codecommit:CreateApprovalRuleTemplate",
45
+ "codecommit:UpdateApprovalRuleTemplateName",
46
+ "codecommit:UpdateApprovalRuleTemplateDescription",
47
+ "codecommit:UpdateApprovalRuleTemplateContent",
48
+ "codecommit:DeleteApprovalRuleTemplate",
49
+ "codecommit:ListRepositoriesForApprovalRuleTemplate",
50
+ "codecommit:GetRepositoryTriggers",
51
+ "codecommit:PutRepositoryTriggers",
52
+ "codecommit:TestRepositoryTriggers",
53
+ "codecommit:GetRepositoryPolicy",
54
+ "codecommit:SetRepositoryPolicy",
55
+ "codecommit:DeleteRepositoryPolicy",
56
+ "codecommit:GetRepositoryTriggers",
57
+ "codecommit:PutRepositoryTriggers",
58
+ "codecommit:TestRepositoryTriggers"
59
+ ],
60
+ "Resource": "*"
61
+ }
62
+ ]
63
+ }
data/guest_list.csv ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Name,Description
2
+ John Park,Pleasure talking to you at AGI house sf today. Best wishes on your future plans and wishing good health to you and your wife and baby in Brazil.
3
+ Adam Wong,"Adam, inspiring to hear you speak at the War in Space gathering at 1515 Folsom this evening. Keep up the great work! Dan "
4
+ Alice Oh,Pleasure listening to your presentation at Adobe's AEP GenAI Seminar today.
5
+ Dustin Calim,
6
+ Dhruv Diddi ⚡,
7
+ Daniel Huang,
8
+ Sean Mudge,"Sean, pleasure meeting you at today’s robotics hackathon. Hope to meet up again since we’re both in the San Jose area. Dan"
9
+ Arash Tajik,
10
+ Sharveen Kumar L,
11
+ Roman Gurovich,
12
+ Nicholas Broad,
13
+ Varin Nair,Pleasure talking with you this evening. Thanks for enlightening me about Factory.
14
+ Eno Reyes,"Eno, Happy to chat with at tonight’s event. Dan"
15
+ Michelle Shen,"Pleasure being the first to the Semianalysis gathering tonight so I could learn more about the organization through you. Hope you had a good evening and best wishes, Dan"
16
+ Shengtan Mao,
17
+ William Chen,
18
+ Aliya Ismagilova,Pleasure meeting you at the Modular hackathon. Best wishes at John Hopkins Physics.
19
+ Nikki Solanki,
20
+ Mohamed Fawzy,Pleasure listening to your insights on training infrastructure at today’s panel discussion at Meta. Hope to catch you at the table talks.
21
+ Hatice Ozen,Pleasure talking to you today about all things Saudi Arabia and Groq. Followed you back on X.
22
+ Div Garg,"Div, Pleasure connecting with you at AGI House tonight. Happy to talk more and best wishes on your research spin-off. Dan"
23
+ Connor Hack,
24
+ Zoe Yan,Pleasure meeting you at the LlamaCon hackathon
25
+ Junseok Oh,
26
+ Kaixing (Kai) Wu,Pleasure meeting you at the LlamaCon hackathon
27
+ "Alan ""Bolo"" Bologlu",Pleasure knowing about you through the article in sfgate today. Sent you a message through your site re: interest in the properties as a collector. Cheers!
28
+ Raymond Liao,
29
+ James Hennessy,"Hi James, we met at the Strong Compute GPU hackathon about a month ago where you presented and we had also talked about a CUDA LLM translation project. Would love to connect. - Dan"
30
+ Maggie Gray,Thanks for sponsoring the NatSec hackathon this past weekend. It was a great event.
31
+ Steven Sloss,
32
+ Isaac Struhl,Fun chatting with you while figuring out the Distributed Spectrum puzzle at Shack15.
33
+ Noah Olsen⚡🦅,
34
+ Daniel Moore,
35
+ Walter Woo,
36
+ Lakshya lnu,Pleasure hitting with you in vball and talking about foundational models.
37
+ Maria Yap,
38
+ Aran Khanna,Pleasure meeting you at the Amplify dinner this evening. Best wishes - Dan
39
+ Shinji Kim,
40
+ Jack Rae,"Jack, really enjoyed your presentation on Friday at AGI House expounding on the state of Thinking models. Best wishes - Dan"
41
+ Karthik Ragunath Ananda Kumar,Algol to smart split video
42
+ Chris Perry,"Hi Chris, I was referred to you by Paige during the Deepmind talks this evening at AGI House. Encountered an error following her demos of the DataScience collab with Gemini and she referred me to you. Best wishes - Dan"
43
+ Paige Bailey,Hi Paige. I just showed you the error on Data Science Collab. Thanks for the talks and the demos - they were very informative. Will send you more details later. - Dan
44
+ Simon Wex,Pleasure chatting with you and ruminating about the future of software development and everything else.
45
+ Ravi Kiran Chirravuri,"Pleasure talking to you at the Roblox mixer this evening. Best wishes, Dan"
46
+ Santosh Alex,Pleasure meeting at the Roblox mixer
47
+ Foad Dabiri,Thanks for enlightening me about the economy group at Roblox. Best wishes - Dan
48
+ Brian Su,Pleasure talking you at the Roblox mixer this evening. Best wishes - Dan
49
+ Chang Xu,Pleasure meeting you at the Roblox mixer. Thanks for elaborating on what you do. - Dan
50
+ "Alireza Boloorchi, PhD",
51
+ Alan Yu,"Alan, I’m an angel investor in the outer space area and would love to connect and share deal flow. I’m going to refer you to Astral Materials who is looking to raise. FYI, I heard about you through Oddbird VC. Cheers, Dan"
52
+ Ashkan Mizani,
53
+ Wayne Crosby,"Wayne, pleasure listening to you this evening and answering my questions around data. Best wishes, Dan"
54
+ Jiya Janowitz,Pleasure meeting you at SSGL at Twin Peaks on Sunday. Best wishes at Astral Materials.
55
+ Jessica Frick,"Hi Jessica, Happy to have met you at Alder's SSGL meetup at Twin Peaks on Sunday and learned about Astral Materials. Please send me a pitch dec at your convenience. Best wishes - Dan"
56
+ Yury Sokolov,
57
+ Jono Ridgway,
58
+ Chase Denecke,Great pleasure talking to you at the Space Meetup at 200 Palo Alto. Fascinating to hear the ways you’re working with stem cells and embryos. Best wishes on your startup . Dan
59
+ Jeffrey Morgan,
60
+ Grace Brown,Pleasure meeting you at the Ollama event at YC
61
+ Woosuk Kwon,Looking forward to seeing you at the meetup at YC
62
+ Michelle Oh,"Pleasure connecting with you this evening- would definitely be interested in your next funding round, assuming you haven’t already blown up after the a16z event. Best wishes. Dan"
63
+ Hamza Adil,
64
+ Sophia Aryan,"Sophia, Pleasure attending you and BuzzRobot’s interview of Peter Norvig this evening. Hope to attend more events in the future. Dan"
65
+ Tingyu Zhang,
66
+ Rahman Hajiyev,
67
+ Burkay Gur,
68
+ Driss Guessous,Pleasure talking to you at Beyond CUDA today. Best wishes at Meta LA. Dan
69
+ Eugene Cheah,"Eugene, Pleasure listening to you at today’s hot takes discussion. Dan"
70
+ Alex Zhang,"Alex, pleasure hearing you talk this evening at gpu mode and hearing your opinions on TK vs cutlass. Best wishes, Dan"
71
+ Lantao Yu,
72
+ Brian Hirsh,
73
+ Eddie Yan,
74
+ Jeffrey Wang,Pleasure listening to your talk today at AGI House SF
75
+ Jennifer Smith,Pleasure talking to you at FYSK this evening about Scribe and the EM positions. Best wishes. Dan
76
+ Melisa Tokmak,
77
+ Walden Yan,
78
+ Chris Yeh,Thanks for the book signing this morning ( I was the last signing that brought his own book ) at AGI House and the very valuable workshop. Re: being the emotional crutch for solopreneurs not being scalable - I guess it’s ai agents to the rescue again!
79
+ Nuno Campos,"Nuno, pleasure meeting you at AGI House today."
80
+ Zade Ismael,
81
+ Ryan Scott,
82
+ Kurtis Evan David,
83
+ Marius Seritan,
84
+ Judy Abad,
85
+ Quinn Li O'Shea,
86
+ Ashley Fieglein Johnson,
87
+ James Le,"James, I just participated in the multimodal hackathon at Weights and Biases and saw your demo with ApertureData re: Video Semantic Search. Happy to connect and hope to chat IRL when TwelveLabs has an event coming up in the Bay Area. Cheers! Dan"
88
+ Valentina Todeschi ,
89
+ Shail Highbloom,
90
+ Aman Gour,
91
+ Nan Xia,
92
+ Victor Wang,
93
+ Aidin Khosrowshahi,
94
+ Rahul Tarak,
95
+ "Joanna Zhang SE, PE, PMP, LEED AP",
96
+ Will Yin,
97
+ Eric Arnoldy,
98
+ Lynne Guey,
99
+ Michael Pierantozzi,
100
+ Mark Saroufim,"Mark, pleasure meeting you at the Nvidia meetup this evening and talking about Cudamode. Best wishes, Dan"
101
+ Rachel Jordan,Pleasure meeting you at the Nvidia CUDA meetup
102
+ Anshuman Bhat,
103
+ Jayram A. Deshpande,Nvidia meetup. Space logistics
104
+ Jake Hemstad,
105
+ Holden Karau,
106
+ Ashish Thusoo,"Hi Ashish, just replied to your email. Would love to connect. Cheers - Dan"
107
+ Nathan Zhao,
108
+ Jung Moon,
109
+ Nick Pezzotti,
110
+ Gary Yao,
111
+ Clovis Vinant-Tang,
112
+ Zack Li,
113
+ Kyle Stratis,Pleasure meeting you at Chip's virtual get together. Best wishes on your consulting practice.
114
+ Leonard Lee,
115
+ "Fei (Kelvin) Chu, MS, MBA",
116
+ "Jack Luo, MD",
117
+ 🪡 Sam Stowers,
118
+ Daniel Han-Chen,
119
+ Yvonne Lung,"Hi Yvonne, happy to honor your friend request on FB. Cool that Lone Moon is investing in Dallas, TX. I'm also in a syndicate that invests in multifamily apartments in Dallas. Cheers and Happy New Year! Dan"
120
+ Brandon Wang,
121
+ shahin.farshchi@luxcapital.com,"Hi Shahin, enjoyed your answers at the panel discussion in the Alumni Ventures offices a few weeks ago. Wanted to chat about outer space companies, but you had to leave early. Best wishes, Dan"
122
+ Vitaly Bulatov,
requirements.txt ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ flask==3.0.0
2
+ flask-cors==4.0.0
3
+ werkzeug==3.1.3
4
+ gunicorn==21.2.0
requirements_hf.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ gradio>=4.0.0
run.py ADDED
@@ -0,0 +1,82 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ LinkedIn Party Planner - Startup Script
4
+ """
5
+
6
+ import sys
7
+ import os
8
+
9
+ def check_dependencies():
10
+ """Check if required dependencies are installed"""
11
+ try:
12
+ import flask
13
+ import selenium
14
+ import webdriver_manager
15
+ print("✅ All dependencies are installed")
16
+ return True
17
+ except ImportError as e:
18
+ print(f"❌ Missing dependency: {e}")
19
+ print("Please run: pip install -r requirements.txt")
20
+ return False
21
+
22
+ def check_chrome():
23
+ """Check if Chrome browser is available"""
24
+ import subprocess
25
+ try:
26
+ # Try to find Chrome on different platforms
27
+ chrome_paths = [
28
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", # macOS
29
+ "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe", # Windows
30
+ "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe", # Windows 32-bit
31
+ "/usr/bin/google-chrome", # Linux
32
+ "/usr/bin/chromium-browser" # Linux Chromium
33
+ ]
34
+
35
+ for path in chrome_paths:
36
+ if os.path.exists(path):
37
+ print("✅ Chrome browser found")
38
+ return True
39
+
40
+ # Try using 'which' command
41
+ result = subprocess.run(['which', 'google-chrome'], capture_output=True, text=True)
42
+ if result.returncode == 0:
43
+ print("✅ Chrome browser found")
44
+ return True
45
+
46
+ print("⚠️ Chrome browser not found in common locations")
47
+ print("Please make sure Chrome is installed and accessible")
48
+ return False
49
+
50
+ except Exception as e:
51
+ print(f"⚠️ Could not verify Chrome installation: {e}")
52
+ return True # Assume it's available
53
+
54
+ def main():
55
+ """Main startup function"""
56
+ print("🚀 LinkedIn Party Planner")
57
+ print("=" * 40)
58
+
59
+ # Check dependencies
60
+ if not check_dependencies():
61
+ sys.exit(1)
62
+
63
+ # Check Chrome
64
+ check_chrome()
65
+
66
+ print("\n📋 Starting the application...")
67
+ print("🌐 The app will be available at: http://localhost:5000")
68
+ print("📖 For help, see README.md")
69
+ print("\n" + "=" * 40)
70
+
71
+ # Import and run the app
72
+ try:
73
+ from app import app
74
+ app.run(debug=True, host='0.0.0.0', port=5000)
75
+ except KeyboardInterrupt:
76
+ print("\n👋 Application stopped by user")
77
+ except Exception as e:
78
+ print(f"\n❌ Error starting application: {e}")
79
+ sys.exit(1)
80
+
81
+ if __name__ == "__main__":
82
+ main()
sample_guests.csv ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name,message
2
+ John Smith,Software Engineer at TechCorp
3
+ Sarah Johnson,Marketing Director at Creative Agency
4
+ Michael Brown,CEO of StartupXYZ
5
+ Emily Davis,Data Scientist at AI Labs
6
+ David Wilson,Product Manager at Innovation Inc
7
+ Lisa Anderson,Sales Director at Growth Co
8
+ Robert Taylor,UX Designer at Design Studio
9
+ Jennifer Martinez,Finance Manager at Finance Corp
10
+ Christopher Lee,Operations Director at Logistics Ltd
11
+ Amanda White,Content Strategist at Media Group
templates/index.html ADDED
@@ -0,0 +1,779 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Party Planner - Guest Table Arranger</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ color: #333;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1200px;
23
+ margin: 0 auto;
24
+ padding: 20px;
25
+ }
26
+
27
+ .header {
28
+ text-align: center;
29
+ color: white;
30
+ margin-bottom: 40px;
31
+ }
32
+
33
+ .header h1 {
34
+ font-size: 2.5rem;
35
+ margin-bottom: 10px;
36
+ font-weight: 300;
37
+ }
38
+
39
+ .header p {
40
+ font-size: 1.1rem;
41
+ opacity: 0.9;
42
+ }
43
+
44
+ .card {
45
+ background: white;
46
+ border-radius: 12px;
47
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
48
+ padding: 30px;
49
+ margin-bottom: 30px;
50
+ }
51
+
52
+ .upload-form {
53
+ max-width: 500px;
54
+ margin: 0 auto;
55
+ }
56
+
57
+ .form-group {
58
+ margin-bottom: 20px;
59
+ }
60
+
61
+ .form-group label {
62
+ display: block;
63
+ margin-bottom: 8px;
64
+ font-weight: 500;
65
+ color: #333;
66
+ }
67
+
68
+ .file-upload-container {
69
+ border: 2px dashed #667eea;
70
+ border-radius: 8px;
71
+ padding: 40px 20px;
72
+ text-align: center;
73
+ background: #f8f9ff;
74
+ transition: all 0.3s ease;
75
+ cursor: pointer;
76
+ }
77
+
78
+ .file-upload-container:hover {
79
+ border-color: #5a6fd8;
80
+ background: #f0f2ff;
81
+ }
82
+
83
+ .file-upload-container.dragover {
84
+ border-color: #5a6fd8;
85
+ background: #e8ecff;
86
+ }
87
+
88
+ .file-upload-icon {
89
+ font-size: 3rem;
90
+ color: #667eea;
91
+ margin-bottom: 15px;
92
+ }
93
+
94
+ .file-upload-text {
95
+ font-size: 1.1rem;
96
+ color: #667eea;
97
+ margin-bottom: 10px;
98
+ }
99
+
100
+ .file-upload-hint {
101
+ font-size: 0.9rem;
102
+ color: #666;
103
+ }
104
+
105
+ .file-input {
106
+ display: none;
107
+ }
108
+
109
+ .btn {
110
+ background: #667eea;
111
+ color: white;
112
+ border: none;
113
+ padding: 12px 24px;
114
+ border-radius: 8px;
115
+ font-size: 16px;
116
+ font-weight: 500;
117
+ cursor: pointer;
118
+ transition: background-color 0.3s ease;
119
+ width: 100%;
120
+ }
121
+
122
+ .btn:hover {
123
+ background: #5a6fd8;
124
+ }
125
+
126
+ .btn:disabled {
127
+ background: #ccc;
128
+ cursor: not-allowed;
129
+ }
130
+
131
+ .btn-secondary {
132
+ background: #6c757d;
133
+ }
134
+
135
+ .btn-secondary:hover {
136
+ background: #545b62;
137
+ }
138
+
139
+ .btn-success {
140
+ background: #28a745;
141
+ }
142
+
143
+ .btn-success:hover {
144
+ background: #1e7e34;
145
+ }
146
+
147
+ .guests-grid {
148
+ display: grid;
149
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
150
+ gap: 20px;
151
+ margin-bottom: 30px;
152
+ }
153
+
154
+ .guest-card {
155
+ border: 2px solid #e1e5e9;
156
+ border-radius: 8px;
157
+ padding: 20px;
158
+ transition: all 0.3s ease;
159
+ cursor: pointer;
160
+ }
161
+
162
+ .guest-card:hover {
163
+ border-color: #667eea;
164
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
165
+ }
166
+
167
+ .guest-card.selected {
168
+ border-color: #667eea;
169
+ background: #f8f9ff;
170
+ }
171
+
172
+ .guest-checkbox {
173
+ margin-right: 12px;
174
+ transform: scale(1.2);
175
+ }
176
+
177
+ .guest-name {
178
+ font-weight: 600;
179
+ font-size: 1.1rem;
180
+ margin-bottom: 8px;
181
+ color: #667eea;
182
+ }
183
+
184
+ .guest-title {
185
+ color: #666;
186
+ font-size: 0.9rem;
187
+ line-height: 1.4;
188
+ }
189
+
190
+ .tables-container {
191
+ display: grid;
192
+ grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
193
+ gap: 40px;
194
+ margin-top: 30px;
195
+ }
196
+
197
+ .table-card {
198
+ background: #f8f9fa;
199
+ border-radius: 12px;
200
+ padding: 25px;
201
+ border: 2px solid #e1e5e9;
202
+ }
203
+
204
+ .table-header {
205
+ text-align: center;
206
+ margin-bottom: 30px;
207
+ padding-bottom: 15px;
208
+ border-bottom: 2px solid #667eea;
209
+ }
210
+
211
+ .table-title {
212
+ font-size: 1.3rem;
213
+ font-weight: 600;
214
+ color: #667eea;
215
+ margin-bottom: 5px;
216
+ }
217
+
218
+ .table-count {
219
+ color: #666;
220
+ font-size: 0.9rem;
221
+ }
222
+
223
+ .circular-table {
224
+ position: relative;
225
+ width: 300px;
226
+ height: 300px;
227
+ margin: 0 auto 30px;
228
+ border-radius: 50%;
229
+ background: linear-gradient(145deg, #e6e6e6, #ffffff);
230
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
231
+ border: 3px solid #667eea;
232
+ }
233
+
234
+ .table-center {
235
+ position: absolute;
236
+ top: 50%;
237
+ left: 50%;
238
+ transform: translate(-50%, -50%);
239
+ width: 60px;
240
+ height: 60px;
241
+ background: #667eea;
242
+ border-radius: 50%;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ color: white;
247
+ font-weight: 600;
248
+ font-size: 0.9rem;
249
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
250
+ }
251
+
252
+ .seat {
253
+ position: absolute;
254
+ width: 50px;
255
+ height: 50px;
256
+ background: #28a745;
257
+ border-radius: 50%;
258
+ display: flex;
259
+ align-items: center;
260
+ justify-content: center;
261
+ color: white;
262
+ font-size: 0.7rem;
263
+ font-weight: 600;
264
+ text-align: center;
265
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
266
+ transition: all 0.3s ease;
267
+ cursor: pointer;
268
+ }
269
+
270
+ .seat:hover {
271
+ transform: scale(1.1);
272
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
273
+ }
274
+
275
+ .seat-info {
276
+ position: absolute;
277
+ bottom: -60px;
278
+ left: 50%;
279
+ transform: translateX(-50%);
280
+ background: #333;
281
+ color: white;
282
+ padding: 8px 12px;
283
+ border-radius: 6px;
284
+ font-size: 0.8rem;
285
+ white-space: nowrap;
286
+ opacity: 0;
287
+ transition: opacity 0.3s ease;
288
+ pointer-events: none;
289
+ z-index: 10;
290
+ }
291
+
292
+ .seat-info::before {
293
+ content: '';
294
+ position: absolute;
295
+ top: -5px;
296
+ left: 50%;
297
+ transform: translateX(-50%);
298
+ border-left: 5px solid transparent;
299
+ border-right: 5px solid transparent;
300
+ border-bottom: 5px solid #333;
301
+ }
302
+
303
+ .seat:hover .seat-info {
304
+ opacity: 1;
305
+ }
306
+
307
+ .stats-panel {
308
+ background: #667eea;
309
+ color: white;
310
+ padding: 20px;
311
+ border-radius: 12px;
312
+ margin-bottom: 30px;
313
+ text-align: center;
314
+ }
315
+
316
+ .stats-grid {
317
+ display: grid;
318
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
319
+ gap: 20px;
320
+ margin-top: 15px;
321
+ }
322
+
323
+ .stat-item {
324
+ text-align: center;
325
+ }
326
+
327
+ .stat-number {
328
+ font-size: 2rem;
329
+ font-weight: 600;
330
+ margin-bottom: 5px;
331
+ }
332
+
333
+ .stat-label {
334
+ font-size: 0.9rem;
335
+ opacity: 0.9;
336
+ }
337
+
338
+ .loading {
339
+ text-align: center;
340
+ padding: 40px;
341
+ color: #666;
342
+ }
343
+
344
+ .spinner {
345
+ border: 4px solid #f3f3f3;
346
+ border-top: 4px solid #667eea;
347
+ border-radius: 50%;
348
+ width: 40px;
349
+ height: 40px;
350
+ animation: spin 1s linear infinite;
351
+ margin: 0 auto 20px;
352
+ }
353
+
354
+ @keyframes spin {
355
+ 0% { transform: rotate(0deg); }
356
+ 100% { transform: rotate(360deg); }
357
+ }
358
+
359
+ .error-message {
360
+ background: #f8d7da;
361
+ color: #721c24;
362
+ padding: 15px;
363
+ border-radius: 8px;
364
+ margin-bottom: 20px;
365
+ border: 1px solid #f5c6cb;
366
+ }
367
+
368
+ .success-message {
369
+ background: #d4edda;
370
+ color: #155724;
371
+ padding: 15px;
372
+ border-radius: 8px;
373
+ margin-bottom: 20px;
374
+ border: 1px solid #c3e6cb;
375
+ }
376
+
377
+ .csv-format-info {
378
+ background: #e7f3ff;
379
+ border: 1px solid #b3d9ff;
380
+ border-radius: 8px;
381
+ padding: 20px;
382
+ margin-bottom: 20px;
383
+ }
384
+
385
+ .csv-format-info h3 {
386
+ color: #0056b3;
387
+ margin-bottom: 10px;
388
+ }
389
+
390
+ .csv-format-info ul {
391
+ margin-left: 20px;
392
+ color: #333;
393
+ }
394
+
395
+ .csv-format-info li {
396
+ margin-bottom: 5px;
397
+ }
398
+
399
+ .hidden {
400
+ display: none;
401
+ }
402
+
403
+ @media (max-width: 768px) {
404
+ .container {
405
+ padding: 10px;
406
+ }
407
+
408
+ .header h1 {
409
+ font-size: 2rem;
410
+ }
411
+
412
+ .guests-grid {
413
+ grid-template-columns: 1fr;
414
+ }
415
+
416
+ .tables-container {
417
+ grid-template-columns: 1fr;
418
+ }
419
+
420
+ .circular-table {
421
+ width: 250px;
422
+ height: 250px;
423
+ }
424
+
425
+ .seat {
426
+ width: 40px;
427
+ height: 40px;
428
+ font-size: 0.6rem;
429
+ }
430
+ }
431
+ </style>
432
+ </head>
433
+ <body>
434
+ <div class="container">
435
+ <div class="header">
436
+ <h1>Party Planner</h1>
437
+ <p>Upload your guest list and arrange them into perfect networking tables</p>
438
+ </div>
439
+
440
+ <!-- Upload Section -->
441
+ <div class="card" id="uploadSection">
442
+ <div class="upload-form">
443
+ <div class="csv-format-info">
444
+ <h3>📋 CSV Format Requirements</h3>
445
+ <ul>
446
+ <li>Your CSV should have exactly 2 columns: name and message</li>
447
+ <li>The first column should contain guest names</li>
448
+ <li>The second column should contain descriptions, roles, or messages</li>
449
+ <li>Entries with blank names or descriptions will be automatically filtered out</li>
450
+ <li>No limit on the number of guests - tables will be created as needed</li>
451
+ </ul>
452
+ </div>
453
+
454
+ <div class="form-group">
455
+ <div class="file-upload-container" id="fileUploadContainer">
456
+ <div class="file-upload-icon">📁</div>
457
+ <div class="file-upload-text">Click to upload or drag and drop</div>
458
+ <div class="file-upload-hint">CSV files only</div>
459
+ <input type="file" id="csvFile" class="file-input" accept=".csv" />
460
+ </div>
461
+ </div>
462
+
463
+ <button class="btn" id="uploadBtn" disabled>Upload Guest List</button>
464
+ </div>
465
+ </div>
466
+
467
+ <!-- Loading Section -->
468
+ <div class="card hidden" id="loadingSection">
469
+ <div class="loading">
470
+ <div class="spinner"></div>
471
+ <p>Processing your guest list...</p>
472
+ </div>
473
+ </div>
474
+
475
+ <!-- Guest Selection Section -->
476
+ <div class="card hidden" id="guestSection">
477
+ <h2 style="margin-bottom: 20px; color: #667eea;">Select Guests for Your Event</h2>
478
+
479
+ <div class="stats-panel">
480
+ <div class="stats-grid">
481
+ <div class="stat-item">
482
+ <div class="stat-number" id="totalGuests">0</div>
483
+ <div class="stat-label">Total Guests</div>
484
+ </div>
485
+ <div class="stat-item">
486
+ <div class="stat-number" id="selectedGuests">0</div>
487
+ <div class="stat-label">Selected</div>
488
+ </div>
489
+ <div class="stat-item">
490
+ <div class="stat-number" id="remainingSlots">0</div>
491
+ <div class="stat-label">Tables Needed</div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+
496
+ <div style="text-align: center; margin-bottom: 20px;">
497
+ <button class="btn btn-secondary" id="selectAllBtn" style="width: auto; margin-right: 10px;">Select All</button>
498
+ <button class="btn btn-secondary" id="deselectAllBtn" style="width: auto;">Deselect All</button>
499
+ </div>
500
+
501
+ <div class="guests-grid" id="guestsGrid">
502
+ <!-- Guest cards will be populated here -->
503
+ </div>
504
+
505
+ <div style="text-align: center; margin-top: 30px;">
506
+ <button class="btn btn-success" id="arrangeBtn" disabled>Arrange Tables</button>
507
+ </div>
508
+ </div>
509
+
510
+ <!-- Tables Section -->
511
+ <div class="card hidden" id="tablesSection">
512
+ <h2 style="margin-bottom: 20px; color: #667eea;">Your Event Tables</h2>
513
+
514
+ <div class="tables-container" id="tablesContainer">
515
+ <!-- Tables will be populated here -->
516
+ </div>
517
+
518
+ <div style="text-align: center; margin-top: 30px;">
519
+ <button class="btn btn-secondary" id="backToGuestsBtn">Back to Guest Selection</button>
520
+ </div>
521
+ </div>
522
+ </div>
523
+
524
+ <script>
525
+ let guests = [];
526
+ let selectedGuests = [];
527
+
528
+ // File upload handling
529
+ const fileUploadContainer = document.getElementById('fileUploadContainer');
530
+ const csvFileInput = document.getElementById('csvFile');
531
+ const uploadBtn = document.getElementById('uploadBtn');
532
+
533
+ fileUploadContainer.addEventListener('click', () => {
534
+ csvFileInput.click();
535
+ });
536
+
537
+ fileUploadContainer.addEventListener('dragover', (e) => {
538
+ e.preventDefault();
539
+ fileUploadContainer.classList.add('dragover');
540
+ });
541
+
542
+ fileUploadContainer.addEventListener('dragleave', () => {
543
+ fileUploadContainer.classList.remove('dragover');
544
+ });
545
+
546
+ fileUploadContainer.addEventListener('drop', (e) => {
547
+ e.preventDefault();
548
+ fileUploadContainer.classList.remove('dragover');
549
+
550
+ const files = e.dataTransfer.files;
551
+ if (files.length > 0) {
552
+ csvFileInput.files = files;
553
+ handleFileSelect();
554
+ }
555
+ });
556
+
557
+ csvFileInput.addEventListener('change', handleFileSelect);
558
+
559
+ function handleFileSelect() {
560
+ const file = csvFileInput.files[0];
561
+ if (file) {
562
+ uploadBtn.disabled = false;
563
+ fileUploadContainer.querySelector('.file-upload-text').textContent = `Selected: ${file.name}`;
564
+ } else {
565
+ uploadBtn.disabled = true;
566
+ fileUploadContainer.querySelector('.file-upload-text').textContent = 'Click to upload or drag and drop';
567
+ }
568
+ }
569
+
570
+ uploadBtn.addEventListener('click', uploadCSV);
571
+
572
+ async function uploadCSV() {
573
+ const file = csvFileInput.files[0];
574
+ if (!file) return;
575
+
576
+ const formData = new FormData();
577
+ formData.append('file', file);
578
+
579
+ showSection('loadingSection');
580
+
581
+ try {
582
+ const response = await fetch('/upload-csv', {
583
+ method: 'POST',
584
+ body: formData
585
+ });
586
+
587
+ const result = await response.json();
588
+
589
+ if (result.success) {
590
+ guests = result.guests;
591
+ selectedGuests = [];
592
+ displayGuests();
593
+ showSection('guestSection');
594
+ showMessage(result.message, 'success');
595
+ } else {
596
+ showSection('uploadSection');
597
+ showMessage(result.error, 'error');
598
+ }
599
+ } catch (error) {
600
+ showSection('uploadSection');
601
+ showMessage('Upload failed. Please try again.', 'error');
602
+ }
603
+ }
604
+
605
+ function displayGuests() {
606
+ const guestsGrid = document.getElementById('guestsGrid');
607
+ guestsGrid.innerHTML = '';
608
+
609
+ guests.forEach(guest => {
610
+ const guestCard = document.createElement('div');
611
+ guestCard.className = 'guest-card';
612
+ guestCard.onclick = () => toggleGuestSelection(guest);
613
+
614
+ guestCard.innerHTML = `
615
+ <input type="checkbox" class="guest-checkbox" ${selectedGuests.includes(guest.id) ? 'checked' : ''}>
616
+ <div class="guest-name">${guest.name}</div>
617
+ <div class="guest-title">${guest.title}</div>
618
+ `;
619
+
620
+ guestsGrid.appendChild(guestCard);
621
+ });
622
+
623
+ updateStats();
624
+ }
625
+
626
+ function toggleGuestSelection(guest) {
627
+ const index = selectedGuests.indexOf(guest.id);
628
+ if (index > -1) {
629
+ selectedGuests.splice(index, 1);
630
+ } else {
631
+ selectedGuests.push(guest.id);
632
+ }
633
+
634
+ displayGuests();
635
+ }
636
+
637
+ function updateStats() {
638
+ document.getElementById('totalGuests').textContent = guests.length;
639
+ document.getElementById('selectedGuests').textContent = selectedGuests.length;
640
+
641
+ // Calculate number of tables needed
642
+ const numTables = Math.ceil(selectedGuests.length / 10);
643
+ document.getElementById('remainingSlots').textContent = numTables;
644
+
645
+ document.getElementById('arrangeBtn').disabled = selectedGuests.length === 0;
646
+ }
647
+
648
+ document.getElementById('arrangeBtn').addEventListener('click', arrangeTables);
649
+
650
+ async function arrangeTables() {
651
+ const selectedGuestObjects = guests.filter(guest => selectedGuests.includes(guest.id));
652
+
653
+ try {
654
+ const response = await fetch('/arrange-tables', {
655
+ method: 'POST',
656
+ headers: {
657
+ 'Content-Type': 'application/json'
658
+ },
659
+ body: JSON.stringify({
660
+ selected_guests: selectedGuestObjects
661
+ })
662
+ });
663
+
664
+ const result = await response.json();
665
+
666
+ if (result.success) {
667
+ displayTables(result.tables);
668
+ showSection('tablesSection');
669
+ } else {
670
+ showMessage(result.error, 'error');
671
+ }
672
+ } catch (error) {
673
+ showMessage('Failed to arrange tables. Please try again.', 'error');
674
+ }
675
+ }
676
+
677
+ function displayTables(tables) {
678
+ const tablesContainer = document.getElementById('tablesContainer');
679
+ tablesContainer.innerHTML = '';
680
+
681
+ tables.forEach((table, index) => {
682
+ const tableCard = document.createElement('div');
683
+ tableCard.className = 'table-card';
684
+
685
+ // Create circular table visualization
686
+ const circularTable = createCircularTable(table, index + 1);
687
+
688
+ tableCard.innerHTML = `
689
+ <div class="table-header">
690
+ <div class="table-title">Table ${index + 1}</div>
691
+ <div class="table-count">${table.length} guests</div>
692
+ </div>
693
+ ${circularTable}
694
+ `;
695
+
696
+ tablesContainer.appendChild(tableCard);
697
+ });
698
+ }
699
+
700
+ function createCircularTable(guests, tableNumber) {
701
+ if (guests.length === 0) {
702
+ return '<div class="circular-table"><div class="table-center">Empty</div></div>';
703
+ }
704
+
705
+ const tableRadius = 120; // Radius of the table
706
+ const seatRadius = 25; // Radius of each seat
707
+ const centerX = 150;
708
+ const centerY = 150;
709
+
710
+ let tableHTML = `
711
+ <div class="circular-table">
712
+ <div class="table-center">Table ${tableNumber}</div>
713
+ `;
714
+
715
+ // Calculate positions for seats around the table
716
+ guests.forEach((guest, index) => {
717
+ const angle = (index / guests.length) * 2 * Math.PI - Math.PI / 2; // Start from top
718
+ const x = centerX + tableRadius * Math.cos(angle);
719
+ const y = centerY + tableRadius * Math.sin(angle);
720
+
721
+ // Get initials for display
722
+ const initials = guest.name.split(' ').map(n => n[0]).join('').toUpperCase();
723
+
724
+ tableHTML += `
725
+ <div class="seat" style="left: ${x - seatRadius}px; top: ${y - seatRadius}px;">
726
+ ${initials}
727
+ <div class="seat-info">
728
+ <strong>${guest.name}</strong><br>
729
+ ${guest.title}
730
+ </div>
731
+ </div>
732
+ `;
733
+ });
734
+
735
+ tableHTML += '</div>';
736
+ return tableHTML;
737
+ }
738
+
739
+ document.getElementById('backToGuestsBtn').addEventListener('click', () => {
740
+ showSection('guestSection');
741
+ });
742
+
743
+ function showSection(sectionId) {
744
+ document.querySelectorAll('.card').forEach(card => {
745
+ card.classList.add('hidden');
746
+ });
747
+ document.getElementById(sectionId).classList.remove('hidden');
748
+ }
749
+
750
+ function showMessage(message, type) {
751
+ const existingMessage = document.querySelector('.error-message, .success-message');
752
+ if (existingMessage) {
753
+ existingMessage.remove();
754
+ }
755
+
756
+ const messageDiv = document.createElement('div');
757
+ messageDiv.className = type === 'error' ? 'error-message' : 'success-message';
758
+ messageDiv.textContent = message;
759
+
760
+ const container = document.querySelector('.container');
761
+ container.insertBefore(messageDiv, container.firstChild);
762
+
763
+ setTimeout(() => {
764
+ messageDiv.remove();
765
+ }, 5000);
766
+ }
767
+
768
+ document.getElementById('selectAllBtn').addEventListener('click', () => {
769
+ selectedGuests = guests.map(guest => guest.id);
770
+ displayGuests();
771
+ });
772
+
773
+ document.getElementById('deselectAllBtn').addEventListener('click', () => {
774
+ selectedGuests = [];
775
+ displayGuests();
776
+ });
777
+ </script>
778
+ </body>
779
+ </html>