Tolga Kavukcu commited on
Commit
7b3dda5
·
1 Parent(s): e3122c4

Initial commit

Browse files
Files changed (10) hide show
  1. .env.example +13 -0
  2. .gitignore +350 -0
  3. DEPLOYMENT.md +62 -0
  4. _old_app.py +704 -0
  5. ai-agent-ui/index.html +221 -0
  6. ai-agent-ui/script.js +562 -0
  7. ai-agent-ui/style.css +561 -0
  8. app.py +297 -50
  9. get_tools_working.py +303 -0
  10. requirements.txt +10 -1
.env.example ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables for AI Lead Generation deployment
2
+
3
+ # SendGrid API key for email functionality
4
+ # Get this from your SendGrid account at https://app.sendgrid.com/settings/api_keys
5
+ SENDGRID_API_KEY=your_sendgrid_api_key_here
6
+
7
+ # Optional: AWS credentials for Bedrock (if not using IAM roles)
8
+ # AWS_ACCESS_KEY_ID=your_aws_access_key
9
+ # AWS_SECRET_ACCESS_KEY=your_aws_secret_key
10
+ # AWS_DEFAULT_REGION=us-west-2
11
+
12
+ # Optional: Other configuration
13
+ # BYPASS_TOOL_CONSENT=true
.gitignore ADDED
@@ -0,0 +1,350 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment variables
2
+ .env
3
+ .env.local
4
+ .env.development.local
5
+ .env.test.local
6
+ .env.production.local
7
+
8
+ # Python
9
+ __pycache__/
10
+ *.py[cod]
11
+ *$py.class
12
+ *.so
13
+ .Python
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib/
21
+ lib64/
22
+ parts/
23
+ sdist/
24
+ var/
25
+ wheels/
26
+ share/python-wheels/
27
+ *.egg-info/
28
+ .installed.cfg
29
+ *.egg
30
+ MANIFEST
31
+
32
+ # PyInstaller
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+ cover/
54
+
55
+ # Translations
56
+ *.mo
57
+ *.pot
58
+
59
+ # Django stuff:
60
+ *.log
61
+ local_settings.py
62
+ db.sqlite3
63
+ db.sqlite3-journal
64
+
65
+ # Flask stuff:
66
+ instance/
67
+ .webassets-cache
68
+
69
+ # Scrapy stuff:
70
+ .scrapy
71
+
72
+ # Sphinx documentation
73
+ docs/_build/
74
+
75
+ # PyBuilder
76
+ .pybuilder/
77
+ target/
78
+
79
+ # Jupyter Notebook
80
+ .ipynb_checkpoints
81
+
82
+ # IPython
83
+ profile_default/
84
+ ipython_config.py
85
+
86
+ # pyenv
87
+ .python-version
88
+
89
+ # pipenv
90
+ Pipfile.lock
91
+
92
+ # poetry
93
+ poetry.lock
94
+
95
+ # pdm
96
+ .pdm.toml
97
+
98
+ # PEP 582
99
+ __pypackages__/
100
+
101
+ # Celery stuff
102
+ celerybeat-schedule
103
+ celerybeat.pid
104
+
105
+ # SageMath parsed files
106
+ *.sage.py
107
+
108
+ # Environments
109
+ .env
110
+ .venv
111
+ env/
112
+ venv/
113
+ ENV/
114
+ env.bak/
115
+ venv.bak/
116
+
117
+ # Spyder project settings
118
+ .spyderproject
119
+ .spyproject
120
+
121
+ # Rope project settings
122
+ .ropeproject
123
+
124
+ # mkdocs documentation
125
+ /site
126
+
127
+ # mypy
128
+ .mypy_cache/
129
+ .dmypy.json
130
+ dmypy.json
131
+
132
+ # Pyre type checker
133
+ .pyre/
134
+
135
+ # pytype static type analyzer
136
+ .pytype/
137
+
138
+ # Cython debug symbols
139
+ cython_debug/
140
+
141
+ # PyCharm
142
+ .idea/
143
+
144
+ # VS Code
145
+ .vscode/
146
+ *.code-workspace
147
+
148
+ # Sublime Text
149
+ *.sublime-project
150
+ *.sublime-workspace
151
+
152
+ # Vim
153
+ *.swp
154
+ *.swo
155
+ *~
156
+
157
+ # Emacs
158
+ *~
159
+ \#*\#
160
+ /.emacs.desktop
161
+ /.emacs.desktop.lock
162
+ *.elc
163
+ auto-save-list
164
+ tramp
165
+ .\#*
166
+
167
+ # macOS
168
+ .DS_Store
169
+ .AppleDouble
170
+ .LSOverride
171
+ Icon?
172
+ ._*
173
+ .DocumentRevisions-V100
174
+ .fseventsd
175
+ .Spotlight-V100
176
+ .TemporaryItems
177
+ .Trashes
178
+ .VolumeIcon.icns
179
+ .com.apple.timemachine.donotpresent
180
+ .AppleDB
181
+ .AppleDesktop
182
+ Network Trash Folder
183
+ Temporary Items
184
+ .apdisk
185
+
186
+ # Windows
187
+ Thumbs.db
188
+ Thumbs.db:encryptable
189
+ ehthumbs.db
190
+ ehthumbs_vista.db
191
+ *.tmp
192
+ *.temp
193
+ *.log
194
+ *.bak
195
+ *.swp
196
+ *.lnk
197
+ Desktop.ini
198
+ $RECYCLE.BIN/
199
+ *.cab
200
+ *.msi
201
+ *.msix
202
+ *.msm
203
+ *.msp
204
+ *.exe
205
+
206
+ # Linux
207
+ *~
208
+ .fuse_hidden*
209
+ .directory
210
+ .Trash-*
211
+ .nfs*
212
+
213
+ # Node.js (for any frontend dependencies)
214
+ node_modules/
215
+ npm-debug.log*
216
+ yarn-debug.log*
217
+ yarn-error.log*
218
+
219
+ # Logs
220
+ logs
221
+ *.log
222
+ npm-debug.log*
223
+ yarn-debug.log*
224
+ yarn-error.log*
225
+ lerna-debug.log*
226
+ .pnpm-debug.log*
227
+
228
+ # Runtime data
229
+ pids
230
+ *.pid
231
+ *.seed
232
+ *.pid.lock
233
+
234
+ # Directory for instrumented libs generated by jscoverage/JSCover
235
+ lib-cov
236
+
237
+ # Coverage directory used by tools like istanbul
238
+ coverage
239
+ *.lcov
240
+
241
+ # nyc test coverage
242
+ .nyc_output
243
+
244
+ # Dependency directories
245
+ node_modules/
246
+ jspm_packages/
247
+
248
+ # Optional npm cache directory
249
+ .npm
250
+
251
+ # Optional eslint cache
252
+ .eslintcache
253
+
254
+ # Microbundle cache
255
+ .rpt2_cache/
256
+ .rts2_cache_cjs/
257
+ .rts2_cache_es/
258
+ .rts2_cache_umd/
259
+
260
+ # Optional REPL history
261
+ .node_repl_history
262
+
263
+ # Output of 'npm pack'
264
+ *.tgz
265
+
266
+ # Yarn Integrity file
267
+ .yarn-integrity
268
+
269
+ # dotenv environment variables file
270
+ .env
271
+ .env.test
272
+ .env.production
273
+
274
+ # parcel-bundler cache (https://parceljs.org/)
275
+ .cache
276
+ .parcel-cache
277
+
278
+ # Next.js build output
279
+ .next
280
+ out
281
+
282
+ # Nuxt.js build / generate output
283
+ .nuxt
284
+ dist
285
+
286
+ # Gatsby files
287
+ .cache/
288
+ public
289
+
290
+ # Storybook build outputs
291
+ .out
292
+ .storybook-out
293
+
294
+ # Temporary folders
295
+ tmp/
296
+ temp/
297
+
298
+ # API keys and secrets (additional protection)
299
+ **/secrets/
300
+ **/secret/
301
+ **/*secret*
302
+ **/*key*
303
+ **/*token*
304
+ **/*password*
305
+ **/config/local.py
306
+ **/config/production.py
307
+
308
+ # Database files
309
+ *.db
310
+ *.sqlite
311
+ *.sqlite3
312
+
313
+ # AI/ML specific
314
+ *.model
315
+ *.pkl
316
+ *.pickle
317
+ model/
318
+ models/
319
+ checkpoints/
320
+ *.h5
321
+ *.hdf5
322
+ *.pt
323
+ *.pth
324
+ *.onnx
325
+
326
+ # Data files
327
+ data/
328
+ datasets/
329
+ *.csv
330
+ *.json
331
+ *.xml
332
+ *.xlsx
333
+ *.xls
334
+ !**/sample*.csv
335
+ !**/example*.json
336
+
337
+ # Backup files
338
+ *.backup
339
+ *.bak
340
+ *~
341
+
342
+ # Archive files
343
+ *.zip
344
+ *.tar
345
+ *.tar.gz
346
+ *.rar
347
+ *.7z
348
+
349
+ # Gradio
350
+ .gradio/
DEPLOYMENT.md ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Deployment Guide
2
+
3
+ ## Hugging Face Spaces Deployment
4
+
5
+ ### 1. Environment Variables
6
+
7
+ Set the following environment variables in your Hugging Face Space settings:
8
+
9
+ #### Required:
10
+ - `SENDGRID_API_KEY`: Your SendGrid API key for email functionality
11
+ - Get this from https://app.sendgrid.com/settings/api_keys
12
+ - Must have Mail Send permissions
13
+
14
+ #### Optional (for AWS Bedrock):
15
+ - `AWS_ACCESS_KEY_ID`: Your AWS access key
16
+ - `AWS_SECRET_ACCESS_KEY`: Your AWS secret key
17
+ - `AWS_DEFAULT_REGION`: AWS region (default: us-west-2)
18
+
19
+ ### 2. Hugging Face Space Configuration
20
+
21
+ 1. **Create a new Space** on Hugging Face
22
+ 2. **Select Framework**: Gradio
23
+ 3. **Add environment variables** in Space settings
24
+ 4. **Upload your code** or connect via Git
25
+
26
+ ### 3. Required Files
27
+
28
+ Ensure these files are in your repository:
29
+ - `app_api.py` - Main application entry point
30
+ - `get_tools_working.py` - Core agent logic
31
+ - `ai-agent-ui/` - Frontend UI files
32
+ - `requirements.txt` - Python dependencies
33
+
34
+ ### 4. Entry Point
35
+
36
+ The main entry point is `app_api.py`. Hugging Face will automatically detect and run this file.
37
+
38
+ ### 5. Access URLs
39
+
40
+ After deployment, your application will be available at:
41
+ - **Main API**: `https://your-space-name.hf.space/`
42
+ - **Frontend UI**: `https://your-space-name.hf.space/ai-agent-ui/`
43
+
44
+ ### 6. Testing
45
+
46
+ 1. Check that the server starts successfully
47
+ 2. Test email functionality with a valid email address
48
+ 3. Verify RSS feed processing works
49
+ 4. Test the complete lead generation workflow
50
+
51
+ ### 7. Troubleshooting
52
+
53
+ - **Email not sending**: Check SENDGRID_API_KEY is set correctly
54
+ - **AWS errors**: Verify AWS credentials and region
55
+ - **RSS errors**: Check network connectivity and feed URLs
56
+ - **UI not loading**: Ensure static files are properly served
57
+
58
+ ### 8. Security Notes
59
+
60
+ - Never commit API keys to the repository
61
+ - Use environment variables for all sensitive configuration
62
+ - The application uses environment variables for secure deployment
_old_app.py ADDED
@@ -0,0 +1,704 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from get_tools_working import get_leadgen_agent_and_prompt
3
+ import re
4
+
5
+ # Initialize agent and default prompt
6
+ agent, default_prompt = get_leadgen_agent_and_prompt()
7
+
8
+ # Default RSS feeds
9
+ default_rss_feeds = """https://techfundingnews.com/feed
10
+ https://feeds.feedburner.com/vcnewsdaily
11
+ https://news.crunchbase.com/feed/"""
12
+
13
+ # Default user preferences
14
+ default_preferences = """Focus Areas:
15
+ - Tech startups and SaaS companies
16
+ - Series A and Series B funding rounds
17
+ - Companies in North America and Europe
18
+
19
+ Scoring Preferences:
20
+ - Prioritize companies with explicit hiring signals
21
+ - Higher scores for funding rounds above $10M
22
+ - Focus on B2B companies over B2C
23
+ - Prefer companies in high-growth sectors (AI, fintech, healthcare tech)"""
24
+
25
+ def validate_email(email):
26
+ """Validate email format"""
27
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
28
+ return re.match(pattern, email) is not None
29
+
30
+ def parse_rss_urls(rss_text):
31
+ """Parse RSS URLs from text input"""
32
+ urls = []
33
+ for line in rss_text.strip().split('\n'):
34
+ line = line.strip()
35
+ if line and (line.startswith('http://') or line.startswith('https://')):
36
+ urls.append(line)
37
+ return urls
38
+
39
+ def create_enhanced_prompt(system_prompt, rss_urls, preferences, email):
40
+ """Create enhanced prompt with all user inputs"""
41
+ rss_url_list = parse_rss_urls(rss_urls)
42
+ rss_urls_str = ' '.join(rss_url_list)
43
+
44
+ enhanced_prompt = f"""RSS Feed URLs: {rss_urls_str}
45
+
46
+ {system_prompt}
47
+
48
+ User Preferences:
49
+ {preferences}
50
+
51
+ Send the report to: {email}"""
52
+
53
+ return enhanced_prompt
54
+
55
+ # Create custom professional theme using compatible properties
56
+ custom_theme = gr.themes.Soft(
57
+ primary_hue="blue",
58
+ secondary_hue="slate",
59
+ neutral_hue="slate",
60
+ text_size="sm",
61
+ spacing_size="md",
62
+ radius_size="md",
63
+ font=[
64
+ gr.themes.GoogleFont("Inter"),
65
+ "ui-sans-serif",
66
+ "system-ui",
67
+ "sans-serif"
68
+ ],
69
+ font_mono=[
70
+ gr.themes.GoogleFont("JetBrains Mono"),
71
+ "ui-monospace",
72
+ "Consolas",
73
+ "monospace"
74
+ ]
75
+ )
76
+
77
+ # Professional CSS with animations and modern styling
78
+ css = """
79
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
80
+
81
+ /* Global container styling */
82
+ .gradio-container {
83
+ max-width: 1400px !important;
84
+ margin: 0 auto !important;
85
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%) !important;
86
+ min-height: 100vh;
87
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
88
+ }
89
+
90
+ /* Animated header with professional styling */
91
+ .professional-header {
92
+ background: linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%);
93
+ padding: 48px 32px;
94
+ margin: -20px -20px 32px -20px;
95
+ border-radius: 0 0 24px 24px;
96
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
97
+ text-align: center;
98
+ position: relative;
99
+ overflow: hidden;
100
+ }
101
+
102
+ .professional-header::before {
103
+ content: '';
104
+ position: absolute;
105
+ top: 0;
106
+ left: 0;
107
+ right: 0;
108
+ bottom: 0;
109
+ background: radial-gradient(circle at 30% 20%, rgba(59, 130, 246, 0.3) 0%, transparent 50%),
110
+ radial-gradient(circle at 70% 80%, rgba(139, 92, 246, 0.2) 0%, transparent 50%);
111
+ animation: pulse-gradient 6s ease-in-out infinite;
112
+ }
113
+
114
+ @keyframes pulse-gradient {
115
+ 0%, 100% { opacity: 0.5; }
116
+ 50% { opacity: 0.8; }
117
+ }
118
+
119
+ .professional-header h1 {
120
+ color: white;
121
+ font-size: 3.5rem;
122
+ font-weight: 700;
123
+ margin: 0 0 16px 0;
124
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
125
+ position: relative;
126
+ z-index: 2;
127
+ letter-spacing: -0.025em;
128
+ }
129
+
130
+ .professional-header p {
131
+ color: rgba(255, 255, 255, 0.9);
132
+ font-size: 1.25rem;
133
+ margin: 0;
134
+ font-weight: 400;
135
+ position: relative;
136
+ z-index: 2;
137
+ line-height: 1.6;
138
+ }
139
+
140
+ /* Professional card styling */
141
+ .config-section {
142
+ background: white;
143
+ border-radius: 8px;
144
+ padding: 20px;
145
+ margin: 12px 0;
146
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
147
+ border: 1px solid rgba(226, 232, 240, 0.8);
148
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
149
+ position: relative;
150
+ overflow: hidden;
151
+ }
152
+
153
+ .config-section::before {
154
+ content: '';
155
+ position: absolute;
156
+ top: 0;
157
+ left: 0;
158
+ right: 0;
159
+ height: 4px;
160
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
161
+ border-radius: 8px 8px 0 0;
162
+ }
163
+
164
+ .config-section:hover {
165
+ transform: translateY(-2px);
166
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
167
+ }
168
+
169
+ .section-header {
170
+ display: flex;
171
+ align-items: center;
172
+ gap: 12px;
173
+ font-size: 1.25rem;
174
+ font-weight: 600;
175
+ color: #1e293b;
176
+ margin-bottom: 16px;
177
+ padding-bottom: 10px;
178
+ border-bottom: 2px solid #f1f5f9;
179
+ }
180
+
181
+ .section-header .emoji {
182
+ font-size: 1.5rem;
183
+ filter: none;
184
+ background: none;
185
+ -webkit-background-clip: unset;
186
+ -webkit-text-fill-color: unset;
187
+ background-clip: unset;
188
+ }
189
+
190
+ /* Info boxes with modern styling */
191
+ .info-card {
192
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
193
+ border: 1px solid #e2e8f0;
194
+ border-left: 4px solid #3b82f6;
195
+ padding: 16px 20px;
196
+ margin: 16px 0;
197
+ border-radius: 8px;
198
+ font-size: 0.875rem;
199
+ line-height: 1.6;
200
+ color: #475569;
201
+ position: relative;
202
+ }
203
+
204
+ .info-card::before {
205
+ content: '💡';
206
+ position: absolute;
207
+ top: 16px;
208
+ right: 20px;
209
+ font-size: 1.25rem;
210
+ opacity: 0.6;
211
+ }
212
+
213
+ /* Status indicators */
214
+ .status-indicator {
215
+ padding: 12px 20px;
216
+ border-radius: 12px;
217
+ margin: 16px 0;
218
+ font-weight: 500;
219
+ font-size: 0.9rem;
220
+ display: flex;
221
+ align-items: center;
222
+ gap: 8px;
223
+ transition: all 0.3s ease;
224
+ }
225
+
226
+ .status-success {
227
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
228
+ color: white;
229
+ box-shadow: 0 4px 14px 0 rgba(16, 185, 129, 0.4);
230
+ }
231
+
232
+ .status-error {
233
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
234
+ color: white;
235
+ box-shadow: 0 4px 14px 0 rgba(239, 68, 68, 0.4);
236
+ }
237
+
238
+ .status-info {
239
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
240
+ color: white;
241
+ box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.4);
242
+ }
243
+
244
+ /* Enhanced button styling */
245
+ .primary-button {
246
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important;
247
+ color: white !important;
248
+ font-weight: 600 !important;
249
+ padding: 10px 20px !important;
250
+ border-radius: 6px !important;
251
+ border: none !important;
252
+ font-size: 0.9rem !important;
253
+ box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.4) !important;
254
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
255
+ position: relative !important;
256
+ overflow: hidden !important;
257
+ margin: 0 0 20px 0 !important;
258
+ display: block !important;
259
+ }
260
+
261
+ /* Fix button container alignment */
262
+ .primary-button {
263
+ width: 100% !important;
264
+ min-width: 0 !important;
265
+ flex-shrink: 1 !important;
266
+ }
267
+
268
+ .primary-button::before {
269
+ content: '';
270
+ position: absolute;
271
+ top: 0;
272
+ left: -100%;
273
+ width: 100%;
274
+ height: 100%;
275
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
276
+ transition: left 0.5s;
277
+ }
278
+
279
+ .primary-button:hover {
280
+ background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%) !important;
281
+ transform: translateY(-2px) !important;
282
+ box-shadow: 0 8px 25px -5px rgba(59, 130, 246, 0.6) !important;
283
+ }
284
+
285
+ .primary-button:hover::before {
286
+ left: 100%;
287
+ }
288
+
289
+ /* Control panel styling */
290
+ .control-panel {
291
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
292
+ border-radius: 16px;
293
+ padding: 18px;
294
+ border: 1px solid #e2e8f0;
295
+ box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.06);
296
+ }
297
+
298
+ /* Enhanced input styling */
299
+ .gradio-textbox, .gradio-dropdown {
300
+ border-radius: 8px !important;
301
+ border: 1px solid #d1d5db !important;
302
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1) !important;
303
+ transition: all 0.3s ease !important;
304
+ }
305
+
306
+ .gradio-textbox:focus, .gradio-dropdown:focus {
307
+ border-color: #3b82f6 !important;
308
+ box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
309
+ outline: none !important;
310
+ }
311
+
312
+ /* Progress indicators */
313
+ .progress-card {
314
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
315
+ border: 1px solid #e2e8f0;
316
+ border-left: 4px solid #3b82f6;
317
+ padding: 16px;
318
+ margin: 16px 0;
319
+ border-radius: 8px;
320
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.06);
321
+ }
322
+
323
+ /* Chatbot styling */
324
+ .chatbot-container {
325
+ background: white !important;
326
+ border-radius: 16px !important;
327
+ border: 1px solid #e2e8f0 !important;
328
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1) !important;
329
+ }
330
+
331
+ /* Animation for loading states */
332
+ @keyframes shimmer {
333
+ 0% { transform: translateX(-100%); }
334
+ 100% { transform: translateX(100%); }
335
+ }
336
+
337
+ .loading-shimmer {
338
+ position: relative;
339
+ overflow: hidden;
340
+ }
341
+
342
+ .loading-shimmer::after {
343
+ content: '';
344
+ position: absolute;
345
+ top: 0;
346
+ right: 0;
347
+ bottom: 0;
348
+ left: 0;
349
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.6), transparent);
350
+ animation: shimmer 2s infinite;
351
+ }
352
+
353
+ /* Responsive design */
354
+ @media (max-width: 768px) {
355
+ .professional-header h1 {
356
+ font-size: 2.5rem;
357
+ }
358
+
359
+ .config-section {
360
+ padding: 20px;
361
+ margin: 12px 0;
362
+ }
363
+ }
364
+
365
+ /* Custom scrollbar */
366
+ ::-webkit-scrollbar {
367
+ width: 8px;
368
+ }
369
+
370
+ ::-webkit-scrollbar-track {
371
+ background: #f1f5f9;
372
+ border-radius: 4px;
373
+ }
374
+
375
+ ::-webkit-scrollbar-thumb {
376
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
377
+ border-radius: 4px;
378
+ }
379
+
380
+ ::-webkit-scrollbar-thumb:hover {
381
+ background: linear-gradient(135deg, #2563eb, #7c3aed);
382
+ }
383
+ """
384
+
385
+ # JavaScript for enhanced interactions
386
+ js = """
387
+ function addInteractiveEffects() {
388
+ // Add ripple effect to buttons
389
+ document.addEventListener('click', function(e) {
390
+ if (e.target.classList.contains('primary-button')) {
391
+ const ripple = document.createElement('span');
392
+ const rect = e.target.getBoundingClientRect();
393
+ const size = Math.max(rect.width, rect.height);
394
+ const x = e.clientX - rect.left - size / 2;
395
+ const y = e.clientY - rect.top - size / 2;
396
+
397
+ ripple.style.width = ripple.style.height = size + 'px';
398
+ ripple.style.left = x + 'px';
399
+ ripple.style.top = y + 'px';
400
+ ripple.classList.add('ripple');
401
+
402
+ e.target.appendChild(ripple);
403
+
404
+ setTimeout(() => {
405
+ ripple.remove();
406
+ }, 600);
407
+ }
408
+ });
409
+
410
+ // Add smooth scrolling for better UX
411
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
412
+ anchor.addEventListener('click', function (e) {
413
+ e.preventDefault();
414
+ const target = document.querySelector(this.getAttribute('href'));
415
+ if (target) {
416
+ target.scrollIntoView({
417
+ behavior: 'smooth',
418
+ block: 'start'
419
+ });
420
+ }
421
+ });
422
+ });
423
+
424
+ return "Interactive effects initialized";
425
+ }
426
+ """
427
+
428
+ # Create the professional Gradio interface
429
+ with gr.Blocks(
430
+ css=css,
431
+ js=js,
432
+ title="🎯 AI Lead Generation Agent - Professional Dashboard",
433
+ theme=custom_theme
434
+ ) as demo:
435
+
436
+ # Header
437
+ gr.HTML("""
438
+ <div class="professional-header">
439
+ <h1><span class="emoji">🎯</span> AI Lead Generation Agent</h1>
440
+ <p>Automated lead generation from funding announcements and hiring signals</p>
441
+ </div>
442
+ """)
443
+
444
+ with gr.Row():
445
+ with gr.Column(scale=2):
446
+ # Email Configuration Section
447
+ with gr.Group(elem_classes=["config-section"]):
448
+ gr.HTML('<div class="section-header"><span class="emoji">📧</span>Email Configuration</div>')
449
+ gr.HTML('<div class="info-card">Enter your email address to receive lead generation reports.</div>')
450
+
451
+ email_input = gr.Textbox(
452
+ label="Email Address",
453
+ placeholder="your.email@company.com",
454
+ info="Enter a valid email address for report delivery",
455
+ elem_id="email-input"
456
+ )
457
+
458
+ # RSS Feed Configuration Section
459
+ with gr.Group(elem_classes=["config-section"]):
460
+ gr.HTML('<div class="section-header"><span class="emoji">📡</span>Data Sources</div>')
461
+ gr.HTML('<div class="info-card">Configure RSS feed sources for monitoring funding announcements and hiring signals.</div>')
462
+
463
+ rss_feeds_input = gr.Textbox(
464
+ label="RSS Feed URLs",
465
+ value=default_rss_feeds,
466
+ lines=6,
467
+ placeholder="https://example.com/feed\nhttps://another-source.com/rss",
468
+ info="One URL per line"
469
+ )
470
+
471
+ # AI Configuration Section
472
+ with gr.Group(elem_classes=["config-section"]):
473
+ gr.HTML('<div class="section-header"><span class="emoji">🤖</span>AI Configuration</div>')
474
+ gr.HTML('<div class="info-card">Customize AI instructions for lead scoring and analysis. The default prompt is optimized for recruitment use cases.</div>')
475
+
476
+ system_prompt_input = gr.Textbox(
477
+ label="AI System Prompt",
478
+ value=default_prompt,
479
+ lines=12,
480
+ info="Modify AI behavior and scoring criteria"
481
+ )
482
+
483
+ # Lead Preferences Section
484
+ with gr.Group(elem_classes=["config-section"]):
485
+ gr.HTML('<div class="section-header"><span class="emoji">⚙️</span>Lead Targeting</div>')
486
+ gr.HTML('<div class="info-card">Define your ideal customer profile and lead scoring preferences for maximum relevance.</div>')
487
+
488
+ preferences_input = gr.Textbox(
489
+ label="Targeting Preferences",
490
+ value=default_preferences,
491
+ lines=8,
492
+ placeholder="Define your target criteria...",
493
+ info="Specify industries, funding stages, geographic regions, and other targeting criteria"
494
+ )
495
+
496
+ with gr.Column(scale=1):
497
+ # Control Panel
498
+ with gr.Group(elem_classes=["control-panel"]):
499
+ gr.HTML('<div class="section-header"><span class="emoji">🚀</span>Mission Control</div>')
500
+
501
+ # Status display
502
+ status_display = gr.HTML('<div class="info-card">Ready to generate leads. Configure your settings and launch the analysis.</div>')
503
+
504
+ # Generate button
505
+ generate_btn = gr.Button(
506
+ "🎯 Generate Report",
507
+ variant="primary",
508
+ size="md",
509
+ elem_classes=["primary-button"]
510
+ )
511
+
512
+ # Progress section
513
+ with gr.Group(elem_classes=["progress-card"]):
514
+ gr.HTML('<div class="section-header" style="margin-bottom: 12px;"><span class="emoji">📊</span>Progress Monitor</div>')
515
+
516
+ progress_display = gr.Textbox(
517
+ label="Status",
518
+ value="Awaiting launch command...",
519
+ interactive=False,
520
+ lines=2,
521
+ elem_id="progress-monitor"
522
+ )
523
+
524
+ # Intelligence Panel
525
+ gr.HTML('''
526
+ <div class="info-card" style="margin-top: 20px;">
527
+ <strong>🎯 Intelligence Capabilities:</strong><br/>
528
+ • Real-time RSS feed monitoring<br/>
529
+ • AI-powered company analysis<br/>
530
+ • Hiring signal detection<br/>
531
+ • Lead scoring & prioritization<br/>
532
+ • Professional report generation
533
+ </div>
534
+ ''')
535
+
536
+ # Best Practices
537
+ gr.HTML('''
538
+ <div class="info-card">
539
+ <strong>📈 Best Practices:</strong><br/>
540
+ • Use industry-specific RSS feeds<br/>
541
+ • Update preferences regularly<br/>
542
+ • Monitor funding announcement timing<br/>
543
+ • Focus on growth-stage companies<br/>
544
+ • Track geographic preferences
545
+ </div>
546
+ ''')
547
+
548
+ # Results Section
549
+ with gr.Group(elem_classes=["config-section"]):
550
+ gr.HTML('<div class="section-header"><span class="emoji">💬</span>AI Agent Activity & Intelligence Feed</div>')
551
+ gr.HTML('<div class="info-card">Monitor real-time AI agent activity, analysis progress, and detailed lead intelligence results.</div>')
552
+
553
+ chatbot = gr.Chatbot(
554
+ label="Lead Intelligence Stream",
555
+ type='messages',
556
+ height=450,
557
+ show_label=True,
558
+ elem_classes=["chatbot-container"],
559
+ placeholder="AI agent will display live analysis and results here..."
560
+ )
561
+
562
+ # State management
563
+ state = gr.State({"email": "", "active": False})
564
+
565
+ def validate_and_generate(email, rss_feeds, system_prompt, preferences, current_state):
566
+ """Validate inputs and generate lead report with enhanced UX"""
567
+
568
+ # Reset chatbot
569
+ chat_history = []
570
+
571
+ # Enhanced validation with better feedback
572
+ if not email or not validate_email(email):
573
+ status = '<div class="status-indicator status-error">❌ Please enter a valid email address</div>'
574
+ progress = "❌ Validation Error: Invalid email format"
575
+ chat_history.append({
576
+ "role": "assistant",
577
+ "content": "❌ **Validation Error**\n\nPlease provide a valid email address to receive your lead intelligence report."
578
+ })
579
+ return status, progress, chat_history, current_state
580
+
581
+ rss_urls = parse_rss_urls(rss_feeds)
582
+ if not rss_urls:
583
+ status = '<div class="status-indicator status-error">❌ Please configure at least one RSS feed source</div>'
584
+ progress = "❌ Configuration Error: No RSS sources"
585
+ chat_history.append({
586
+ "role": "assistant",
587
+ "content": "❌ **Configuration Error**\n\nAt least one valid RSS feed URL is required for lead generation."
588
+ })
589
+ return status, progress, chat_history, current_state
590
+
591
+ if not system_prompt.strip():
592
+ status = '<div class="status-indicator status-error">❌ AI system prompt cannot be empty</div>'
593
+ progress = "❌ Configuration Error: Missing AI prompt"
594
+ chat_history.append({
595
+ "role": "assistant",
596
+ "content": "❌ **Configuration Error**\n\nAI system prompt is required for intelligent lead analysis."
597
+ })
598
+ return status, progress, chat_history, current_state
599
+
600
+ # Update state
601
+ current_state["email"] = email
602
+ current_state["active"] = True
603
+
604
+ # Enhanced progress feedback
605
+ status = '<div class="status-indicator status-success">✅ All systems validated. Initiating AI lead generation...</div>'
606
+ progress = f"🚀 Launching intelligence gathering from {len(rss_urls)} premium sources..."
607
+
608
+ chat_history.append({
609
+ "role": "assistant",
610
+ "content": f"""🚀 **Lead Generation Mission Initiated**
611
+
612
+ 📧 **Target Email:** {email}
613
+ 📡 **Data Sources:** {len(rss_urls)} RSS feeds configured
614
+ 🎯 **Analysis Mode:** Advanced lead scoring enabled
615
+ 🤖 **AI Status:** Ready for intelligent analysis
616
+
617
+ Initiating real-time monitoring and analysis..."""
618
+ })
619
+
620
+ # Create enhanced prompt
621
+ enhanced_prompt = create_enhanced_prompt(system_prompt, rss_feeds, preferences, email)
622
+
623
+ try:
624
+ # Run the agent with enhanced feedback
625
+ chat_history.append({
626
+ "role": "user",
627
+ "content": "🎯 Execute lead generation with current configuration"
628
+ })
629
+
630
+ progress = "🤖 AI Agent processing feeds • Analyzing companies • Scoring leads..."
631
+
632
+ result = agent(enhanced_prompt)
633
+ response = str(result)
634
+
635
+ chat_history.append({
636
+ "role": "assistant",
637
+ "content": f"""✅ **Lead Generation Mission Completed Successfully!**
638
+
639
+ 🎯 **Results Summary:**
640
+ {response}
641
+
642
+ 📧 **Report Status:** Professional lead intelligence report has been generated and sent to {email}
643
+
644
+ 📊 **Next Steps:**
645
+ • Check your email for the detailed report
646
+ • Review lead scores and prioritize outreach
647
+ • Monitor for new funding announcements
648
+ • Update targeting criteria based on results"""
649
+ })
650
+
651
+ status = '<div class="status-indicator status-success">✅ Mission accomplished! Lead intelligence report delivered successfully.</div>'
652
+ progress = f"✅ Complete! Professional report sent to {email}"
653
+
654
+ except Exception as e:
655
+ error_msg = f"""❌ **Mission Error Encountered**
656
+
657
+ **Error Details:** {str(e)}
658
+
659
+ **Troubleshooting Steps:**
660
+ • Verify RSS feed accessibility
661
+ • Check email configuration
662
+ • Ensure stable internet connection
663
+ • Try again with simplified parameters"""
664
+
665
+ chat_history.append({"role": "assistant", "content": error_msg})
666
+
667
+ status = '<div class="status-indicator status-error">❌ Mission error - please review configuration and try again</div>'
668
+ progress = f"❌ Error: {str(e)}"
669
+
670
+ return status, progress, chat_history, current_state
671
+
672
+ # Event handlers with enhanced functionality
673
+ generate_btn.click(
674
+ validate_and_generate,
675
+ inputs=[email_input, rss_feeds_input, system_prompt_input, preferences_input, state],
676
+ outputs=[status_display, progress_display, chatbot, state],
677
+ queue=True
678
+ )
679
+
680
+ # Real-time email validation
681
+ def update_email_status(email):
682
+ if not email:
683
+ return '<div class="info-card">Enter your email address to receive lead reports.</div>'
684
+ elif validate_email(email):
685
+ return '<div class="status-indicator status-success">✅ Valid email address configured</div>'
686
+ else:
687
+ return '<div class="status-indicator status-error">❌ Invalid email format - please correct</div>'
688
+
689
+ email_input.change(
690
+ update_email_status,
691
+ inputs=[email_input],
692
+ outputs=[status_display]
693
+ )
694
+
695
+ if __name__ == "__main__":
696
+ print("🚀 Launching Professional AI Lead Generation Agent...")
697
+ demo.launch(
698
+ share=False,
699
+ server_name="0.0.0.0",
700
+ server_port=7860,
701
+ show_error=True,
702
+ favicon_path=None,
703
+ show_api=False
704
+ )
ai-agent-ui/index.html ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>🎯 AI Lead Generation Agent - Professional Dashboard</title>
7
+
8
+ <!-- Material UI CSS -->
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
10
+ <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
11
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.indigo-pink.min.css">
12
+
13
+ <!-- Custom CSS -->
14
+ <link rel="stylesheet" href="style.css">
15
+ </head>
16
+ <body>
17
+ <!-- Header Section -->
18
+ <header class="professional-header">
19
+ <div class="header-content">
20
+ <h1><span class="material-icons">track_changes</span> AI Lead Generation Agent</h1>
21
+ <p>Automated lead generation from funding announcements and hiring signals</p>
22
+ </div>
23
+ </header>
24
+
25
+ <!-- Main Content -->
26
+ <main class="main-container">
27
+ <div class="content-grid">
28
+ <!-- Left Column - Configuration -->
29
+ <div class="config-column">
30
+ <!-- Email Configuration -->
31
+ <div class="config-section">
32
+ <div class="section-header">
33
+ <span class="material-icons">email</span>
34
+ <h3>Email Configuration</h3>
35
+ </div>
36
+ <div class="info-card">
37
+ <span class="material-icons">info</span>
38
+ Enter your email address to receive lead generation reports.
39
+ </div>
40
+ <div class="input-group">
41
+ <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
42
+ <input class="mdl-textfield__input" type="email" id="email-input" required>
43
+ <label class="mdl-textfield__label" for="email-input">Email Address</label>
44
+ <span class="mdl-textfield__error">Please enter a valid email address</span>
45
+ </div>
46
+ <div class="input-helper">Enter a valid email address for report delivery</div>
47
+ </div>
48
+ </div>
49
+
50
+ <!-- RSS Feed Configuration -->
51
+ <div class="config-section">
52
+ <div class="section-header">
53
+ <span class="material-icons">rss_feed</span>
54
+ <h3>Data Sources</h3>
55
+ </div>
56
+ <div class="info-card">
57
+ <span class="material-icons">info</span>
58
+ Configure RSS feed sources for monitoring funding announcements and hiring signals.
59
+ </div>
60
+ <div class="input-group">
61
+ <div class="mdl-textfield mdl-js-textfield">
62
+ <textarea class="mdl-textfield__input" type="text" rows="6" id="rss-feeds" required></textarea>
63
+ <label class="mdl-textfield__label" for="rss-feeds">RSS Feed URLs</label>
64
+ </div>
65
+ <div class="input-helper">One URL per line</div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- AI Configuration -->
70
+ <div class="config-section">
71
+ <div class="section-header">
72
+ <span class="material-icons">smart_toy</span>
73
+ <h3>AI Configuration</h3>
74
+ </div>
75
+ <div class="info-card">
76
+ <span class="material-icons">info</span>
77
+ Customize AI instructions for lead scoring and analysis. The default prompt is optimized for recruitment use cases.
78
+ </div>
79
+ <div class="input-group">
80
+ <div class="mdl-textfield mdl-js-textfield">
81
+ <textarea class="mdl-textfield__input" type="text" rows="12" id="system-prompt" required></textarea>
82
+ <label class="mdl-textfield__label" for="system-prompt">AI System Prompt</label>
83
+ </div>
84
+ <div class="input-helper">Modify AI behavior and scoring criteria</div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Lead Preferences -->
89
+ <div class="config-section">
90
+ <div class="section-header">
91
+ <span class="material-icons">settings</span>
92
+ <h3>Lead Targeting</h3>
93
+ </div>
94
+ <div class="info-card">
95
+ <span class="material-icons">info</span>
96
+ Define your ideal customer profile and lead scoring preferences for maximum relevance.
97
+ </div>
98
+ <div class="input-group">
99
+ <div class="mdl-textfield mdl-js-textfield">
100
+ <textarea class="mdl-textfield__input" type="text" rows="8" id="preferences" required></textarea>
101
+ <label class="mdl-textfield__label" for="preferences">Targeting Preferences</label>
102
+ </div>
103
+ <div class="input-helper">Specify industries, funding stages, geographic regions, and other targeting criteria</div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+
108
+ <!-- Right Column - Control Panel -->
109
+ <div class="control-column">
110
+ <!-- Mission Control -->
111
+ <div class="control-panel">
112
+ <div class="section-header">
113
+ <span class="material-icons">rocket_launch</span>
114
+ <h3>Mission Control</h3>
115
+ </div>
116
+
117
+ <!-- Status Display -->
118
+ <div class="status-display" id="status-display">
119
+ <div class="status-indicator status-info">
120
+ <span class="material-icons">info</span>
121
+ Ready to generate leads. Configure your settings and launch the analysis.
122
+ </div>
123
+ </div>
124
+
125
+ <!-- Generate Button -->
126
+ <button class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect generate-btn" id="generate-btn">
127
+ <span class="material-icons">track_changes</span>
128
+ Generate Report
129
+ </button>
130
+
131
+ <!-- Progress Monitor -->
132
+ <div class="progress-card">
133
+ <div class="section-header">
134
+ <span class="material-icons">analytics</span>
135
+ <h4>Progress Monitor</h4>
136
+ </div>
137
+ <div class="progress-display" id="progress-display">
138
+ <div class="progress-text">Awaiting launch command...</div>
139
+ <div class="mdl-progress mdl-js-progress" id="progress-bar" style="width: 100%; display: none;"></div>
140
+ </div>
141
+ </div>
142
+
143
+ <!-- Intelligence Capabilities -->
144
+ <div class="progress-card">
145
+ <div class="section-header">
146
+ <span class="material-icons">psychology</span>
147
+ <h4>Intelligence Capabilities</h4>
148
+ </div>
149
+ <ul class="capability-list">
150
+ <li><span class="material-icons">check_circle</span>Real-time RSS feed monitoring</li>
151
+ <li><span class="material-icons">check_circle</span>AI-powered company analysis</li>
152
+ <li><span class="material-icons">check_circle</span>Hiring signal detection</li>
153
+ <li><span class="material-icons">check_circle</span>Lead scoring & prioritization</li>
154
+ <li><span class="material-icons">check_circle</span>Professional report generation</li>
155
+ </ul>
156
+ </div>
157
+
158
+ <!-- Best Practices -->
159
+ <div class="progress-card">
160
+ <div class="section-header">
161
+ <span class="material-icons">trending_up</span>
162
+ <h4>Best Practices</h4>
163
+ </div>
164
+ <ul class="capability-list">
165
+ <li><span class="material-icons">check_circle</span>Use industry-specific RSS feeds</li>
166
+ <li><span class="material-icons">check_circle</span>Update preferences regularly</li>
167
+ <li><span class="material-icons">check_circle</span>Monitor funding announcement timing</li>
168
+ <li><span class="material-icons">check_circle</span>Focus on growth-stage companies</li>
169
+ <li><span class="material-icons">check_circle</span>Track geographic preferences</li>
170
+ </ul>
171
+ </div>
172
+ </div>
173
+ </div>
174
+ </div>
175
+
176
+ <!-- Results Section -->
177
+ <div class="results-section">
178
+ <div class="section-header">
179
+ <span class="material-icons">forum</span>
180
+ <h3>AI Agent Activity & Intelligence Feed</h3>
181
+ </div>
182
+ <div class="info-card">
183
+ <span class="material-icons">info</span>
184
+ Monitor real-time AI agent activity, analysis progress, and detailed lead intelligence results.
185
+ </div>
186
+ <div class="chat-container" id="chat-container">
187
+ <div class="chat-placeholder">
188
+ <span class="material-icons">smart_toy</span>
189
+ <p>AI agent will display live analysis and results here...</p>
190
+ </div>
191
+ </div>
192
+ </div>
193
+ </main>
194
+
195
+ <!-- Loading Overlay -->
196
+ <div class="loading-overlay" id="loading-overlay" style="display: none;">
197
+ <div class="loading-content">
198
+ <div class="mdl-spinner mdl-js-spinner is-active"></div>
199
+ <p>Processing your request...</p>
200
+ </div>
201
+ </div>
202
+
203
+ <!-- Snackbar for notifications -->
204
+ <div class="mdl-snackbar mdl-js-snackbar">
205
+ <div class="mdl-snackbar__text"></div>
206
+ <button class="mdl-snackbar__action" type="button"></button>
207
+ </div>
208
+
209
+ <!-- Material Design Lite JavaScript -->
210
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/material-design-lite/1.3.0/material.min.js"></script>
211
+
212
+ <!-- Gradio Client -->
213
+ <script type="module">
214
+ import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
215
+ window.GradioClient = Client;
216
+ </script>
217
+
218
+ <!-- Custom JavaScript -->
219
+ <script src="script.js"></script>
220
+ </body>
221
+ </html>
ai-agent-ui/script.js ADDED
@@ -0,0 +1,562 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Application Configuration
2
+ const CONFIG = {
3
+ API_BASE_URL: 'http://localhost:7860',
4
+ DEFAULT_RSS_FEEDS: `https://techfundingnews.com/feed
5
+ https://feeds.feedburner.com/vcnewsdaily
6
+ https://news.crunchbase.com/feed/`,
7
+ DEFAULT_PREFERENCES: `Focus Areas:
8
+ - Tech startups and SaaS companies
9
+ - Series A and Series B funding rounds
10
+ - Companies in North America and Europe
11
+
12
+ Scoring Preferences:
13
+ - Prioritize companies with explicit hiring signals
14
+ - Higher scores for funding rounds above $10M
15
+ - Focus on B2B companies over B2C
16
+ - Prefer companies in high-growth sectors (AI, fintech, healthcare tech)`,
17
+ DEFAULT_PROMPT: `You are an AI lead generation agent specialized in analyzing funding announcements and hiring signals to identify potential leads for a recruitment services company.
18
+
19
+ Your task is to:
20
+ 1. Monitor RSS feeds for funding announcements and hiring-related signals
21
+ 2. Analyze companies that have recently received funding or are showing signs of growth
22
+ 3. Score leads based on their likelihood to need recruitment services
23
+ 4. Generate a professional report with actionable insights
24
+
25
+ Analysis Framework:
26
+ - Company funding stage and amount
27
+ - Hiring signals and job postings activity
28
+ - Industry and growth potential
29
+ - Geographic relevance
30
+ - Recent news and market position
31
+
32
+ Scoring Criteria (1-10 scale):
33
+ - 9-10: High priority leads with strong hiring signals and recent funding
34
+ - 7-8: Good prospects with moderate signals
35
+ - 5-6: Potential leads worth monitoring
36
+ - 1-4: Low priority or insufficient data
37
+
38
+ Output a comprehensive report with:
39
+ - Executive summary
40
+ - Top 10 prioritized leads with scores and rationale
41
+ - Market insights and trends
42
+ - Recommended next steps for outreach`
43
+ };
44
+
45
+ // State Management
46
+ class AppState {
47
+ constructor() {
48
+ this.isProcessing = false;
49
+ this.currentRequestId = null;
50
+ this.chatHistory = [];
51
+ this.formData = {
52
+ email: '',
53
+ rssFeeds: CONFIG.DEFAULT_RSS_FEEDS,
54
+ systemPrompt: CONFIG.DEFAULT_PROMPT,
55
+ preferences: CONFIG.DEFAULT_PREFERENCES
56
+ };
57
+ }
58
+
59
+ updateFormData(key, value) {
60
+ this.formData[key] = value;
61
+ this.validateForm();
62
+ }
63
+
64
+ validateForm() {
65
+ const emailValid = this.validateEmail(this.formData.email);
66
+ const rssValid = this.parseRSSUrls(this.formData.rssFeeds).length > 0;
67
+ const promptValid = this.formData.systemPrompt.trim().length > 0;
68
+
69
+ return emailValid && rssValid && promptValid;
70
+ }
71
+
72
+ validateEmail(email) {
73
+ const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
74
+ return pattern.test(email);
75
+ }
76
+
77
+ parseRSSUrls(rssText) {
78
+ const urls = [];
79
+ const lines = rssText.trim().split('\n');
80
+ lines.forEach(line => {
81
+ line = line.trim();
82
+ if (line && (line.startsWith('http://') || line.startsWith('https://'))) {
83
+ urls.push(line);
84
+ }
85
+ });
86
+ return urls;
87
+ }
88
+ }
89
+
90
+ // UI Controller
91
+ class UIController {
92
+ constructor(state) {
93
+ this.state = state;
94
+ this.initializeElements();
95
+ this.setupEventListeners();
96
+ this.populateDefaultValues();
97
+ this.initializeMDL();
98
+ }
99
+
100
+ initializeElements() {
101
+ this.elements = {
102
+ emailInput: document.getElementById('email-input'),
103
+ rssFeeds: document.getElementById('rss-feeds'),
104
+ systemPrompt: document.getElementById('system-prompt'),
105
+ preferences: document.getElementById('preferences'),
106
+ generateBtn: document.getElementById('generate-btn'),
107
+ statusDisplay: document.getElementById('status-display'),
108
+ progressDisplay: document.getElementById('progress-display'),
109
+ chatContainer: document.getElementById('chat-container'),
110
+ loadingOverlay: document.getElementById('loading-overlay'),
111
+ progressBar: document.getElementById('progress-bar')
112
+ };
113
+ }
114
+
115
+ initializeMDL() {
116
+ // Initialize Material Design Lite components
117
+ if (window.componentHandler) {
118
+ window.componentHandler.upgradeAllRegistered();
119
+ }
120
+ }
121
+
122
+ populateDefaultValues() {
123
+ // Clear email field to ensure fresh state
124
+ this.elements.emailInput.value = '';
125
+ this.state.formData.email = '';
126
+
127
+ // Set default values for other fields
128
+ this.elements.rssFeeds.value = CONFIG.DEFAULT_RSS_FEEDS;
129
+ this.elements.systemPrompt.value = CONFIG.DEFAULT_PROMPT;
130
+ this.elements.preferences.value = CONFIG.DEFAULT_PREFERENCES;
131
+
132
+ // Update state to match form values
133
+ this.state.formData.rssFeeds = CONFIG.DEFAULT_RSS_FEEDS;
134
+ this.state.formData.systemPrompt = CONFIG.DEFAULT_PROMPT;
135
+ this.state.formData.preferences = CONFIG.DEFAULT_PREFERENCES;
136
+
137
+ // Trigger MDL updates
138
+ this.elements.emailInput.parentElement.MaterialTextfield?.change();
139
+ this.elements.rssFeeds.parentElement.MaterialTextfield?.change();
140
+ this.elements.systemPrompt.parentElement.MaterialTextfield?.change();
141
+ this.elements.preferences.parentElement.MaterialTextfield?.change();
142
+
143
+ // Update email status after clearing
144
+ this.updateEmailStatus();
145
+ }
146
+
147
+ setupEventListeners() {
148
+ // Form input listeners
149
+ this.elements.emailInput.addEventListener('input', (e) => {
150
+ this.state.updateFormData('email', e.target.value);
151
+ this.updateEmailStatus();
152
+ });
153
+
154
+ this.elements.rssFeeds.addEventListener('input', (e) => {
155
+ this.state.updateFormData('rssFeeds', e.target.value);
156
+ });
157
+
158
+ this.elements.systemPrompt.addEventListener('input', (e) => {
159
+ this.state.updateFormData('systemPrompt', e.target.value);
160
+ });
161
+
162
+ this.elements.preferences.addEventListener('input', (e) => {
163
+ this.state.updateFormData('preferences', e.target.value);
164
+ });
165
+
166
+ // Generate button listener
167
+ this.elements.generateBtn.addEventListener('click', () => {
168
+ this.handleGenerate();
169
+ });
170
+
171
+ // Enter key handling
172
+ document.addEventListener('keydown', (e) => {
173
+ if (e.key === 'Enter' && e.ctrlKey) {
174
+ this.handleGenerate();
175
+ }
176
+ });
177
+ }
178
+
179
+ updateEmailStatus() {
180
+ const email = this.elements.emailInput.value;
181
+ let statusHtml = '';
182
+
183
+ if (!email) {
184
+ statusHtml = `
185
+ <div class="status-indicator status-info">
186
+ <span class="material-icons">info</span>
187
+ Ready to generate leads. Configure your settings and launch the analysis.
188
+ </div>
189
+ `;
190
+ } else if (this.state.validateEmail(email)) {
191
+ statusHtml = `
192
+ <div class="status-indicator status-success">
193
+ <span class="material-icons">check_circle</span>
194
+ Valid email address configured
195
+ </div>
196
+ `;
197
+ } else {
198
+ statusHtml = `
199
+ <div class="status-indicator status-error">
200
+ <span class="material-icons">error</span>
201
+ Invalid email format - please correct
202
+ </div>
203
+ `;
204
+ }
205
+
206
+ this.elements.statusDisplay.innerHTML = statusHtml;
207
+ }
208
+
209
+ updateProgress(message, showProgress = false) {
210
+ const progressText = this.elements.progressDisplay.querySelector('.progress-text');
211
+ progressText.textContent = message;
212
+
213
+ if (showProgress) {
214
+ this.elements.progressBar.style.display = 'block';
215
+ this.elements.progressBar.MaterialProgress?.setProgress(50);
216
+ } else {
217
+ this.elements.progressBar.style.display = 'none';
218
+ }
219
+ }
220
+
221
+ showNotification(message, type = 'info') {
222
+ const snackbar = document.querySelector('.mdl-snackbar');
223
+ if (snackbar && snackbar.MaterialSnackbar) {
224
+ snackbar.MaterialSnackbar.showSnackbar({
225
+ message: message,
226
+ timeout: 4000
227
+ });
228
+ }
229
+ }
230
+
231
+ showLoading(show = true) {
232
+ this.elements.loadingOverlay.style.display = show ? 'flex' : 'none';
233
+ this.elements.generateBtn.disabled = show;
234
+
235
+ if (show) {
236
+ this.elements.generateBtn.innerHTML = `
237
+ <div class="mdl-spinner mdl-js-spinner is-active" style="width: 20px; height: 20px; margin-right: 8px;"></div>
238
+ Processing...
239
+ `;
240
+ } else {
241
+ this.elements.generateBtn.innerHTML = `
242
+ <span class="material-icons">track_changes</span>
243
+ Generate Report
244
+ `;
245
+ }
246
+
247
+ // Re-initialize spinner
248
+ if (show && window.componentHandler) {
249
+ window.componentHandler.upgradeAllRegistered();
250
+ }
251
+ }
252
+
253
+ addChatMessage(content, role = 'assistant') {
254
+ // Remove placeholder if it exists
255
+ const placeholder = this.elements.chatContainer.querySelector('.chat-placeholder');
256
+ if (placeholder) {
257
+ placeholder.remove();
258
+ }
259
+
260
+ const messageDiv = document.createElement('div');
261
+ messageDiv.className = `chat-message ${role}`;
262
+
263
+ const avatarDiv = document.createElement('div');
264
+ avatarDiv.className = `chat-avatar ${role}`;
265
+ avatarDiv.innerHTML = `<span class="material-icons">${role === 'user' ? 'person' : 'smart_toy'}</span>`;
266
+
267
+ const contentDiv = document.createElement('div');
268
+ contentDiv.className = 'chat-content';
269
+
270
+ // Format content for better display
271
+ const formattedContent = this.formatChatContent(content);
272
+ contentDiv.innerHTML = formattedContent;
273
+
274
+ messageDiv.appendChild(avatarDiv);
275
+ messageDiv.appendChild(contentDiv);
276
+
277
+ this.elements.chatContainer.appendChild(messageDiv);
278
+
279
+ // Scroll to bottom
280
+ messageDiv.scrollIntoView({ behavior: 'smooth' });
281
+
282
+ // Store in state
283
+ this.state.chatHistory.push({ role, content });
284
+ }
285
+
286
+ formatChatContent(content) {
287
+ // Convert markdown-style formatting to HTML
288
+ let formatted = content
289
+ .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
290
+ .replace(/\*(.*?)\*/g, '<em>$1</em>')
291
+ .replace(/`(.*?)`/g, '<code>$1</code>')
292
+ .replace(/\n/g, '<br>');
293
+
294
+ // Format lists
295
+ formatted = formatted.replace(/^- (.*?)(<br>|$)/gm, '<li>$1</li>');
296
+ if (formatted.includes('<li>')) {
297
+ formatted = formatted.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
298
+ }
299
+
300
+ return formatted;
301
+ }
302
+
303
+ clearChat() {
304
+ this.elements.chatContainer.innerHTML = `
305
+ <div class="chat-placeholder">
306
+ <span class="material-icons">smart_toy</span>
307
+ <p>AI agent will display live analysis and results here...</p>
308
+ </div>
309
+ `;
310
+ this.state.chatHistory = [];
311
+ }
312
+
313
+ async handleGenerate() {
314
+ if (this.state.isProcessing) {
315
+ return;
316
+ }
317
+
318
+ // Validate form
319
+ const email = this.elements.emailInput.value;
320
+ const rssFeeds = this.elements.rssFeeds.value;
321
+ const systemPrompt = this.elements.systemPrompt.value;
322
+ const preferences = this.elements.preferences.value;
323
+
324
+ if (!this.state.validateEmail(email)) {
325
+ this.updateStatus('error', '❌ Please enter a valid email address');
326
+ this.showNotification('Please enter a valid email address', 'error');
327
+ return;
328
+ }
329
+
330
+ const rssUrls = this.state.parseRSSUrls(rssFeeds);
331
+ if (rssUrls.length === 0) {
332
+ this.updateStatus('error', '❌ Please configure at least one RSS feed source');
333
+ this.showNotification('At least one RSS feed is required', 'error');
334
+ return;
335
+ }
336
+
337
+ if (!systemPrompt.trim()) {
338
+ this.updateStatus('error', '❌ AI system prompt cannot be empty');
339
+ this.showNotification('AI system prompt is required', 'error');
340
+ return;
341
+ }
342
+
343
+ // Start processing
344
+ this.state.isProcessing = true;
345
+ this.showLoading(true);
346
+ this.clearChat();
347
+
348
+ this.updateStatus('success', '✅ All systems validated. Initiating AI lead generation...');
349
+ this.updateProgress(`🚀 Launching intelligence gathering from ${rssUrls.length} premium sources...`, true);
350
+
351
+ // Add initial chat message
352
+ this.addChatMessage(`🚀 **Lead Generation Mission Initiated**
353
+
354
+ 📧 **Target Email:** ${email}
355
+ 📡 **Data Sources:** ${rssUrls.length} RSS feeds configured
356
+ 🎯 **Analysis Mode:** Advanced lead scoring enabled
357
+ 🤖 **AI Status:** Ready for intelligent analysis
358
+
359
+ Initiating real-time monitoring and analysis...`);
360
+
361
+ try {
362
+ // Create enhanced prompt
363
+ const enhancedPrompt = this.createEnhancedPrompt(systemPrompt, rssFeeds, preferences, email);
364
+
365
+ // Add user message to chat
366
+ this.addChatMessage('🎯 Execute lead generation with current configuration', 'user');
367
+
368
+ this.updateProgress('🤖 AI Agent processing feeds • Analyzing companies • Scoring leads...', true);
369
+
370
+ // Make API call
371
+ const result = await this.callGradioAPI(enhancedPrompt);
372
+
373
+ // Add success message
374
+ this.addChatMessage(`✅ **Lead Generation Mission Completed Successfully!**
375
+
376
+ 🎯 **Results Summary:**
377
+ ${result}
378
+
379
+ 📧 **Report Status:** Professional lead intelligence report has been generated and sent to ${email}
380
+
381
+ 📊 **Next Steps:**
382
+ • Check your email for the detailed report
383
+ • Review lead scores and prioritize outreach
384
+ • Monitor for new funding announcements
385
+ • Update targeting criteria based on results`);
386
+
387
+ this.updateStatus('success', '✅ Mission accomplished! Lead intelligence report delivered successfully.');
388
+ this.updateProgress(`✅ Complete! Professional report sent to ${email}`);
389
+ this.showNotification('Lead generation completed successfully!', 'success');
390
+
391
+ } catch (error) {
392
+ console.error('Generation error:', error);
393
+
394
+ const errorMessage = `❌ **Mission Error Encountered**
395
+
396
+ **Error Details:** ${error.message}
397
+
398
+ **Troubleshooting Steps:**
399
+ • Verify RSS feed accessibility
400
+ • Check email configuration
401
+ • Ensure stable internet connection
402
+ • Try again with simplified parameters`;
403
+
404
+ this.addChatMessage(errorMessage);
405
+ this.updateStatus('error', '❌ Mission error - please review configuration and try again');
406
+ this.updateProgress(`❌ Error: ${error.message}`);
407
+ this.showNotification('An error occurred. Please try again.', 'error');
408
+ } finally {
409
+ this.state.isProcessing = false;
410
+ this.showLoading(false);
411
+ }
412
+ }
413
+
414
+ updateStatus(type, message) {
415
+ const iconMap = {
416
+ success: 'check_circle',
417
+ error: 'error',
418
+ info: 'info'
419
+ };
420
+
421
+ const statusHtml = `
422
+ <div class="status-indicator status-${type}">
423
+ <span class="material-icons">${iconMap[type]}</span>
424
+ ${message}
425
+ </div>
426
+ `;
427
+
428
+ this.elements.statusDisplay.innerHTML = statusHtml;
429
+ }
430
+
431
+ createEnhancedPrompt(systemPrompt, rssFeeds, preferences, email) {
432
+ const rssUrls = this.state.parseRSSUrls(rssFeeds);
433
+ const rssUrlsStr = rssUrls.join(' ');
434
+
435
+ // Always add the email instruction before making the call to backend
436
+ const emailInstruction = `
437
+
438
+ IMPORTANT: When you need to send an email with the report, use the email address provided in the user input. Do not use any hardcoded email addresses. The email should be sent to the address specified by the user.
439
+
440
+ Format the report professionally for business stakeholders and ensure the email is professionally formatted with proper HTML styling.`;
441
+
442
+ return `RSS Feed URLs: ${rssUrlsStr}
443
+
444
+ ${systemPrompt}${emailInstruction}
445
+
446
+ User Preferences:
447
+ ${preferences}
448
+
449
+ Send the report to: ${email}`;
450
+ }
451
+
452
+ async callGradioAPI(enhancedPrompt) {
453
+ try {
454
+ // Extract the parameters from the enhanced prompt
455
+ const email = this.state.formData.email;
456
+ const rssFeeds = this.state.formData.rssFeeds;
457
+ const systemPrompt = this.state.formData.systemPrompt;
458
+ const preferences = this.state.formData.preferences;
459
+
460
+ console.log('🔄 Making API call to Gradio server...');
461
+ console.log('📧 Email:', email);
462
+ console.log('📡 RSS count:', this.state.parseRSSUrls(rssFeeds).length);
463
+
464
+ // Wait for GradioClient to be available
465
+ if (!window.GradioClient) {
466
+ throw new Error('Gradio client not loaded. Please refresh the page.');
467
+ }
468
+
469
+ // Connect to Gradio server
470
+ const client = await window.GradioClient.connect(CONFIG.API_BASE_URL);
471
+ console.log('📡 Connected to Gradio server');
472
+
473
+ // Make prediction call
474
+ const result = await client.predict("/generate_lead_report", {
475
+ email: email,
476
+ rss_feeds: rssFeeds,
477
+ system_prompt: systemPrompt,
478
+ preferences: preferences
479
+ });
480
+
481
+ console.log('📦 API Response:', result);
482
+
483
+ // Check if the API call was successful
484
+ if (result && result.data && result.data.length > 0) {
485
+ const apiResponse = result.data[0];
486
+ console.log('✅ API Response data:', apiResponse);
487
+
488
+ if (typeof apiResponse === 'object' && apiResponse.success) {
489
+ return apiResponse.result;
490
+ } else if (typeof apiResponse === 'object' && apiResponse.success === false) {
491
+ throw new Error(apiResponse.message || apiResponse.error || 'API returned error');
492
+ } else if (typeof apiResponse === 'string') {
493
+ // If it's a string response, treat it as the result
494
+ return apiResponse;
495
+ } else {
496
+ throw new Error('Unexpected API response format');
497
+ }
498
+ } else {
499
+ throw new Error('Invalid API response format - no data returned');
500
+ }
501
+
502
+ } catch (error) {
503
+ console.error('❌ API call failed:', error);
504
+
505
+ // If API call fails, provide helpful error messages
506
+ if (error.message.includes('Failed to fetch') || error.message.includes('Network')) {
507
+ throw new Error('Unable to connect to API server. Please ensure the Gradio server is running on localhost:7860');
508
+ } else if (error.message.includes('CORS')) {
509
+ throw new Error('CORS issue detected. Please ensure the Gradio server is running with CORS enabled');
510
+ }
511
+
512
+ throw error;
513
+ }
514
+ }
515
+ }
516
+
517
+ // Initialize Application
518
+ document.addEventListener('DOMContentLoaded', () => {
519
+ // Initialize app state and UI controller
520
+ const appState = new AppState();
521
+ const uiController = new UIController(appState);
522
+
523
+ // Add some initial UI enhancements
524
+ setTimeout(() => {
525
+ // Add ripple effect to cards
526
+ document.querySelectorAll('.config-section').forEach(card => {
527
+ card.addEventListener('mouseenter', function() {
528
+ this.style.transform = 'translateY(-2px)';
529
+ });
530
+
531
+ card.addEventListener('mouseleave', function() {
532
+ this.style.transform = 'translateY(0)';
533
+ });
534
+ });
535
+
536
+ // Add keyboard shortcuts info
537
+ const shortcutInfo = document.createElement('div');
538
+ shortcutInfo.innerHTML = `
539
+ <div class="info-card" style="margin-top: 16px; font-size: 0.8rem;">
540
+ <span class="material-icons">keyboard</span>
541
+ <strong>Keyboard Shortcuts:</strong> Ctrl+Enter to generate report
542
+ </div>
543
+ `;
544
+ document.querySelector('.control-panel').appendChild(shortcutInfo);
545
+
546
+ }, 1000);
547
+
548
+ console.log('🎯 AI Lead Generation Agent - Professional Dashboard Initialized');
549
+ });
550
+
551
+ // Global error handling
552
+ window.addEventListener('error', (event) => {
553
+ console.error('Application error:', event.error);
554
+ });
555
+
556
+ // Service worker registration for offline support (optional)
557
+ if ('serviceWorker' in navigator) {
558
+ window.addEventListener('load', () => {
559
+ // Optional: Register service worker for offline support
560
+ // navigator.serviceWorker.register('/sw.js');
561
+ });
562
+ }
ai-agent-ui/style.css ADDED
@@ -0,0 +1,561 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Global Styles */
2
+ * {
3
+ margin: 0;
4
+ padding: 0;
5
+ box-sizing: border-box;
6
+ }
7
+
8
+ body {
9
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ background: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
11
+ min-height: 100vh;
12
+ color: #1e293b;
13
+ line-height: 1.6;
14
+ }
15
+
16
+ /* Header Styling */
17
+ .professional-header {
18
+ background: linear-gradient(135deg, #1e293b 0%, #334155 50%, #475569 100%);
19
+ padding: 48px 32px;
20
+ text-align: center;
21
+ position: relative;
22
+ overflow: hidden;
23
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
24
+ }
25
+
26
+ .professional-header::before {
27
+ content: '';
28
+ position: absolute;
29
+ top: 0;
30
+ left: 0;
31
+ right: 0;
32
+ bottom: 0;
33
+ background: radial-gradient(circle at 30% 20%, rgba(59, 130, 246, 0.3) 0%, transparent 50%),
34
+ radial-gradient(circle at 70% 80%, rgba(139, 92, 246, 0.2) 0%, transparent 50%);
35
+ animation: pulse-gradient 6s ease-in-out infinite;
36
+ }
37
+
38
+ @keyframes pulse-gradient {
39
+ 0%, 100% { opacity: 0.5; }
40
+ 50% { opacity: 0.8; }
41
+ }
42
+
43
+ .header-content {
44
+ position: relative;
45
+ z-index: 2;
46
+ }
47
+
48
+ .professional-header h1 {
49
+ color: white;
50
+ font-size: 3rem;
51
+ font-weight: 700;
52
+ margin: 0 0 16px 0;
53
+ text-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
54
+ letter-spacing: -0.025em;
55
+ display: flex;
56
+ align-items: center;
57
+ justify-content: center;
58
+ gap: 16px;
59
+ }
60
+
61
+ .professional-header h1 .material-icons {
62
+ font-size: 3rem;
63
+ }
64
+
65
+ .professional-header p {
66
+ color: rgba(255, 255, 255, 0.9);
67
+ font-size: 1.25rem;
68
+ margin: 0;
69
+ font-weight: 400;
70
+ }
71
+
72
+ /* Main Container */
73
+ .main-container {
74
+ max-width: 1400px;
75
+ margin: 0 auto;
76
+ padding: 32px 20px;
77
+ }
78
+
79
+ .content-grid {
80
+ display: grid;
81
+ grid-template-columns: 2fr 1fr;
82
+ gap: 32px;
83
+ margin-bottom: 32px;
84
+ }
85
+
86
+ /* Configuration Sections */
87
+ .config-section {
88
+ background: white;
89
+ border-radius: 12px;
90
+ padding: 24px;
91
+ margin-bottom: 24px;
92
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
93
+ border: 1px solid rgba(226, 232, 240, 0.8);
94
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
95
+ position: relative;
96
+ overflow: hidden;
97
+ }
98
+
99
+ .config-section::before {
100
+ content: '';
101
+ position: absolute;
102
+ top: 0;
103
+ left: 0;
104
+ right: 0;
105
+ height: 4px;
106
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
107
+ border-radius: 12px 12px 0 0;
108
+ }
109
+
110
+ .config-section:hover {
111
+ transform: translateY(-2px);
112
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
113
+ }
114
+
115
+ .section-header {
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 12px;
119
+ margin-bottom: 20px;
120
+ padding-bottom: 12px;
121
+ border-bottom: 2px solid #f1f5f9;
122
+ }
123
+
124
+ .section-header h3, .section-header h4 {
125
+ font-size: 1.25rem;
126
+ font-weight: 600;
127
+ color: #1e293b;
128
+ margin: 0;
129
+ }
130
+
131
+ .section-header h4 {
132
+ font-size: 1.1rem;
133
+ }
134
+
135
+ .section-header .material-icons {
136
+ font-size: 1.5rem;
137
+ color: #3b82f6;
138
+ }
139
+
140
+ /* Info Cards */
141
+ .info-card {
142
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
143
+ border: 1px solid #e2e8f0;
144
+ border-left: 4px solid #3b82f6;
145
+ padding: 16px 20px;
146
+ margin: 16px 0;
147
+ border-radius: 8px;
148
+ font-size: 0.9rem;
149
+ line-height: 1.6;
150
+ color: #475569;
151
+ display: flex;
152
+ align-items: flex-start;
153
+ gap: 12px;
154
+ }
155
+
156
+ .info-card .material-icons {
157
+ color: #3b82f6;
158
+ font-size: 1.2rem;
159
+ margin-top: 2px;
160
+ }
161
+
162
+ /* Input Groups */
163
+ .input-group {
164
+ margin: 20px 0;
165
+ }
166
+
167
+ .input-helper {
168
+ font-size: 0.875rem;
169
+ color: #6b7280;
170
+ margin-top: 8px;
171
+ padding-left: 4px;
172
+ }
173
+
174
+ /* Material Design Overrides */
175
+ .mdl-textfield {
176
+ width: 100%;
177
+ margin: 8px 0;
178
+ }
179
+
180
+ .mdl-textfield__input {
181
+ border-radius: 8px !important;
182
+ font-family: 'Inter', sans-serif !important;
183
+ font-size: 0.9rem !important;
184
+ }
185
+
186
+ .mdl-textfield__label {
187
+ font-family: 'Inter', sans-serif !important;
188
+ font-weight: 500 !important;
189
+ color: #64748b !important;
190
+ }
191
+
192
+ .mdl-textfield--floating-label.is-focused .mdl-textfield__label,
193
+ .mdl-textfield--floating-label.is-dirty .mdl-textfield__label {
194
+ color: #3b82f6 !important;
195
+ }
196
+
197
+ .mdl-textfield__input:focus {
198
+ border-color: #3b82f6 !important;
199
+ }
200
+
201
+ /* Control Panel */
202
+ .control-panel {
203
+ background: white;
204
+ border-radius: 16px;
205
+ padding: 24px;
206
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
207
+ border: 1px solid rgba(226, 232, 240, 0.8);
208
+ position: sticky;
209
+ top: 20px;
210
+ }
211
+
212
+ /* Status Indicators */
213
+ .status-indicator {
214
+ padding: 16px 20px;
215
+ border-radius: 12px;
216
+ margin: 16px 0;
217
+ font-weight: 500;
218
+ font-size: 0.9rem;
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 12px;
222
+ transition: all 0.3s ease;
223
+ }
224
+
225
+ .status-success {
226
+ background: linear-gradient(135deg, #10b981 0%, #059669 100%);
227
+ color: white;
228
+ box-shadow: 0 4px 14px 0 rgba(16, 185, 129, 0.4);
229
+ }
230
+
231
+ .status-error {
232
+ background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
233
+ color: white;
234
+ box-shadow: 0 4px 14px 0 rgba(239, 68, 68, 0.4);
235
+ }
236
+
237
+ .status-info {
238
+ background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
239
+ color: white;
240
+ box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.4);
241
+ }
242
+
243
+ /* Generate Button */
244
+ .generate-btn {
245
+ width: 100% !important;
246
+ background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%) !important;
247
+ color: white !important;
248
+ font-weight: 600 !important;
249
+ padding: 16px 24px !important;
250
+ border-radius: 12px !important;
251
+ border: none !important;
252
+ font-size: 1rem !important;
253
+ box-shadow: 0 4px 14px 0 rgba(59, 130, 246, 0.4) !important;
254
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
255
+ margin: 20px 0 !important;
256
+ display: flex !important;
257
+ align-items: center !important;
258
+ justify-content: center !important;
259
+ gap: 8px !important;
260
+ text-transform: none !important;
261
+ letter-spacing: 0.025em !important;
262
+ }
263
+
264
+ .generate-btn:hover {
265
+ background: linear-gradient(135deg, #2563eb 0%, #1e40af 100%) !important;
266
+ transform: translateY(-2px) !important;
267
+ box-shadow: 0 8px 25px -5px rgba(59, 130, 246, 0.6) !important;
268
+ }
269
+
270
+ /* Progress Card */
271
+ .progress-card {
272
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
273
+ border: 1px solid #e2e8f0;
274
+ border-left: 4px solid #3b82f6;
275
+ padding: 20px;
276
+ margin: 20px 0;
277
+ border-radius: 8px;
278
+ box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.06);
279
+ }
280
+
281
+ .progress-text {
282
+ font-size: 0.9rem;
283
+ color: #475569;
284
+ margin-bottom: 12px;
285
+ }
286
+
287
+ /* Capabilities Lists */
288
+ .capabilities, .best-practices {
289
+ margin-top: 20px;
290
+ }
291
+
292
+ .capability-title {
293
+ text-align: center;
294
+ margin-bottom: 16px;
295
+ font-size: 1.1rem;
296
+ font-weight: 600;
297
+ color: #1e293b;
298
+ }
299
+
300
+ .capability-list {
301
+ list-style: none;
302
+ margin: 0;
303
+ padding: 0;
304
+ }
305
+
306
+ .capability-list li {
307
+ display: flex;
308
+ align-items: center;
309
+ gap: 12px;
310
+ padding: 8px 0;
311
+ font-size: 0.875rem;
312
+ color: #475569;
313
+ }
314
+
315
+ .capability-list li .material-icons {
316
+ color: #10b981;
317
+ font-size: 1rem;
318
+ }
319
+
320
+ /* Results Section */
321
+ .results-section {
322
+ background: white;
323
+ border-radius: 12px;
324
+ padding: 24px;
325
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
326
+ border: 1px solid rgba(226, 232, 240, 0.8);
327
+ position: relative;
328
+ overflow: hidden;
329
+ }
330
+
331
+ .results-section::before {
332
+ content: '';
333
+ position: absolute;
334
+ top: 0;
335
+ left: 0;
336
+ right: 0;
337
+ height: 4px;
338
+ background: linear-gradient(90deg, #3b82f6, #8b5cf6);
339
+ border-radius: 12px 12px 0 0;
340
+ }
341
+
342
+ /* Chat Container */
343
+ .chat-container {
344
+ background: #f8fafc;
345
+ border-radius: 12px;
346
+ min-height: 450px;
347
+ padding: 24px;
348
+ border: 1px solid #e2e8f0;
349
+ margin-top: 16px;
350
+ position: relative;
351
+ }
352
+
353
+ .chat-placeholder {
354
+ display: flex;
355
+ flex-direction: column;
356
+ align-items: center;
357
+ justify-content: center;
358
+ height: 100%;
359
+ min-height: 400px;
360
+ color: #6b7280;
361
+ text-align: center;
362
+ }
363
+
364
+ .chat-placeholder .material-icons {
365
+ font-size: 4rem;
366
+ color: #d1d5db;
367
+ margin-bottom: 16px;
368
+ }
369
+
370
+ .chat-placeholder p {
371
+ font-size: 1.1rem;
372
+ margin: 0;
373
+ }
374
+
375
+ /* Chat Messages */
376
+ .chat-message {
377
+ margin: 16px 0;
378
+ display: flex;
379
+ gap: 12px;
380
+ animation: slideInUp 0.3s ease-out;
381
+ }
382
+
383
+ @keyframes slideInUp {
384
+ from {
385
+ opacity: 0;
386
+ transform: translateY(20px);
387
+ }
388
+ to {
389
+ opacity: 1;
390
+ transform: translateY(0);
391
+ }
392
+ }
393
+
394
+ .chat-message.user {
395
+ flex-direction: row-reverse;
396
+ }
397
+
398
+ .chat-avatar {
399
+ width: 40px;
400
+ height: 40px;
401
+ border-radius: 20px;
402
+ display: flex;
403
+ align-items: center;
404
+ justify-content: center;
405
+ flex-shrink: 0;
406
+ }
407
+
408
+ .chat-avatar.assistant {
409
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
410
+ color: white;
411
+ }
412
+
413
+ .chat-avatar.user {
414
+ background: linear-gradient(135deg, #10b981, #059669);
415
+ color: white;
416
+ }
417
+
418
+ .chat-content {
419
+ background: white;
420
+ padding: 16px 20px;
421
+ border-radius: 16px;
422
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
423
+ border: 1px solid #e2e8f0;
424
+ max-width: 80%;
425
+ line-height: 1.6;
426
+ }
427
+
428
+ .chat-message.user .chat-content {
429
+ background: linear-gradient(135deg, #3b82f6, #2563eb);
430
+ color: white;
431
+ }
432
+
433
+ /* Loading Overlay */
434
+ .loading-overlay {
435
+ position: fixed;
436
+ top: 0;
437
+ left: 0;
438
+ width: 100%;
439
+ height: 100%;
440
+ background: rgba(0, 0, 0, 0.5);
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ z-index: 1000;
445
+ backdrop-filter: blur(4px);
446
+ }
447
+
448
+ .loading-content {
449
+ background: white;
450
+ padding: 40px;
451
+ border-radius: 16px;
452
+ text-align: center;
453
+ box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
454
+ max-width: 400px;
455
+ width: 90%;
456
+ }
457
+
458
+ .loading-content p {
459
+ margin-top: 20px;
460
+ font-size: 1.1rem;
461
+ color: #475569;
462
+ }
463
+
464
+ /* Responsive Design */
465
+ @media (max-width: 1024px) {
466
+ .content-grid {
467
+ grid-template-columns: 1fr;
468
+ gap: 24px;
469
+ }
470
+
471
+ .control-panel {
472
+ position: static;
473
+ }
474
+ }
475
+
476
+ @media (max-width: 768px) {
477
+ .main-container {
478
+ padding: 20px 16px;
479
+ }
480
+
481
+ .professional-header {
482
+ padding: 32px 20px;
483
+ }
484
+
485
+ .professional-header h1 {
486
+ font-size: 2.25rem;
487
+ flex-direction: column;
488
+ gap: 8px;
489
+ }
490
+
491
+ .config-section,
492
+ .control-panel,
493
+ .results-section {
494
+ padding: 20px;
495
+ }
496
+
497
+ .chat-content {
498
+ max-width: 90%;
499
+ }
500
+ }
501
+
502
+ @media (max-width: 480px) {
503
+ .professional-header h1 {
504
+ font-size: 1.875rem;
505
+ }
506
+
507
+ .professional-header p {
508
+ font-size: 1rem;
509
+ }
510
+
511
+ .section-header h3 {
512
+ font-size: 1.1rem;
513
+ }
514
+
515
+ .config-section,
516
+ .control-panel,
517
+ .results-section {
518
+ padding: 16px;
519
+ }
520
+ }
521
+
522
+ /* Custom Scrollbar */
523
+ ::-webkit-scrollbar {
524
+ width: 8px;
525
+ }
526
+
527
+ ::-webkit-scrollbar-track {
528
+ background: #f1f5f9;
529
+ border-radius: 4px;
530
+ }
531
+
532
+ ::-webkit-scrollbar-thumb {
533
+ background: linear-gradient(135deg, #3b82f6, #8b5cf6);
534
+ border-radius: 4px;
535
+ }
536
+
537
+ ::-webkit-scrollbar-thumb:hover {
538
+ background: linear-gradient(135deg, #2563eb, #7c3aed);
539
+ }
540
+
541
+ /* Material Design Lite Overrides */
542
+ .mdl-button--raised {
543
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1) !important;
544
+ }
545
+
546
+ .mdl-button--raised:hover {
547
+ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
548
+ }
549
+
550
+ /* Snackbar Styling */
551
+ .mdl-snackbar {
552
+ background: #1e293b !important;
553
+ color: white !important;
554
+ border-radius: 8px !important;
555
+ box-shadow: 0 4px 14px rgba(0, 0, 0, 0.2) !important;
556
+ }
557
+
558
+ .mdl-snackbar__text {
559
+ font-family: 'Inter', sans-serif !important;
560
+ font-weight: 500 !important;
561
+ }
app.py CHANGED
@@ -1,64 +1,311 @@
1
  import gradio as gr
2
- from huggingface_hub import InferenceClient
 
 
 
 
 
 
 
3
 
4
- """
5
- For more information on `huggingface_hub` Inference API support, please check the docs: https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
6
- """
7
- client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
8
 
 
 
 
 
9
 
10
- def respond(
11
- message,
12
- history: list[tuple[str, str]],
13
- system_message,
14
- max_tokens,
15
- temperature,
16
- top_p,
17
- ):
18
- messages = [{"role": "system", "content": system_message}]
19
 
20
- for val in history:
21
- if val[0]:
22
- messages.append({"role": "user", "content": val[0]})
23
- if val[1]:
24
- messages.append({"role": "assistant", "content": val[1]})
 
25
 
26
- messages.append({"role": "user", "content": message})
27
 
28
- response = ""
 
29
 
30
- for message in client.chat_completion(
31
- messages,
32
- max_tokens=max_tokens,
33
- stream=True,
34
- temperature=temperature,
35
- top_p=top_p,
36
- ):
37
- token = message.choices[0].delta.content
38
 
39
- response += token
40
- yield response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
 
43
- """
44
- For information on how to customize the ChatInterface, peruse the gradio docs: https://www.gradio.app/docs/chatinterface
45
- """
46
- demo = gr.ChatInterface(
47
- respond,
48
- additional_inputs=[
49
- gr.Textbox(value="You are a friendly Chatbot.", label="System message"),
50
- gr.Slider(minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"),
51
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
52
- gr.Slider(
53
- minimum=0.1,
54
- maximum=1.0,
55
- value=0.95,
56
- step=0.05,
57
- label="Top-p (nucleus sampling)",
58
- ),
59
- ],
60
- )
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
 
63
  if __name__ == "__main__":
64
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ from fastapi import FastAPI
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from fastapi.staticfiles import StaticFiles
5
+ from fastapi.responses import FileResponse
6
+ from get_tools_working import get_leadgen_agent_and_prompt
7
+ import re
8
+ import json
9
+ import os
10
 
11
+ # Server is generic - no defaults defined here
12
+ # All defaults are handled by the UI layer for better separation of concerns
 
 
13
 
14
+ def validate_email(email):
15
+ """Validate email format"""
16
+ pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
17
+ return re.match(pattern, email) is not None
18
 
19
+ def parse_rss_urls(rss_text):
20
+ """Parse RSS URLs from text input"""
21
+ urls = []
22
+ for line in rss_text.strip().split('\n'):
23
+ line = line.strip()
24
+ if line and (line.startswith('http://') or line.startswith('https://')):
25
+ urls.append(line)
26
+ return urls
 
27
 
28
+ def create_enhanced_prompt(system_prompt, rss_urls, preferences, email):
29
+ """Create enhanced prompt with all user inputs"""
30
+ rss_url_list = parse_rss_urls(rss_urls)
31
+ rss_urls_str = ' '.join(rss_url_list)
32
+
33
+ enhanced_prompt = f"""RSS Feed URLs: {rss_urls_str}
34
 
35
+ {system_prompt}
36
 
37
+ User Preferences:
38
+ {preferences}
39
 
40
+ Send the report to: {email}"""
41
+
42
+ return enhanced_prompt
 
 
 
 
 
43
 
44
+ def generate_lead_report(email, rss_feeds, system_prompt, preferences):
45
+ """Main API function to generate lead report"""
46
+ try:
47
+ # Enhanced validation with better feedback
48
+ if not email or not validate_email(email):
49
+ return {
50
+ "success": False,
51
+ "error": "Invalid email address",
52
+ "message": "Please provide a valid email address to receive your lead intelligence report."
53
+ }
54
+
55
+ rss_urls = parse_rss_urls(rss_feeds)
56
+ if not rss_urls:
57
+ return {
58
+ "success": False,
59
+ "error": "No RSS sources",
60
+ "message": "At least one valid RSS feed URL is required for lead generation."
61
+ }
62
+
63
+ if not system_prompt.strip():
64
+ return {
65
+ "success": False,
66
+ "error": "Missing AI prompt",
67
+ "message": "AI system prompt is required for intelligent lead analysis."
68
+ }
69
+
70
+ # Create agent with user's email for this request
71
+ agent, prompt_template = get_leadgen_agent_and_prompt(user_email=email)
72
+
73
+ # Create enhanced prompt
74
+ enhanced_prompt = create_enhanced_prompt(system_prompt, rss_feeds, preferences, email)
75
+
76
+ # Run the agent
77
+ result = agent(enhanced_prompt)
78
+ response = str(result)
79
+
80
+ return {
81
+ "success": True,
82
+ "result": response,
83
+ "message": f"Lead intelligence report has been generated and sent to {email}",
84
+ "email": email,
85
+ "sources_count": len(rss_urls)
86
+ }
87
+
88
+ except Exception as e:
89
+ return {
90
+ "success": False,
91
+ "error": str(e),
92
+ "message": "An error occurred during lead generation. Please check your configuration and try again."
93
+ }
94
 
95
+ def get_server_info():
96
+ """API function to get server information"""
97
+ return {
98
+ "success": True,
99
+ "server_info": {
100
+ "name": "AI Lead Generation API",
101
+ "version": "1.0.0",
102
+ "description": "Generic API server for lead generation - configuration handled by UI layer",
103
+ "endpoints": [
104
+ "/generate_lead_report",
105
+ "/validate_configuration"
106
+ ]
107
+ }
108
+ }
109
 
110
+ def validate_configuration(email, rss_feeds, system_prompt, preferences):
111
+ """API function to validate configuration without running the agent"""
112
+ validation_results = {
113
+ "email_valid": validate_email(email) if email else False,
114
+ "rss_valid": len(parse_rss_urls(rss_feeds)) > 0,
115
+ "prompt_valid": bool(system_prompt.strip()),
116
+ "preferences_valid": bool(preferences.strip())
117
+ }
118
+
119
+ validation_results["all_valid"] = all(validation_results.values())
120
+
121
+ return {
122
+ "success": True,
123
+ "validation": validation_results,
124
+ "rss_count": len(parse_rss_urls(rss_feeds))
125
+ }
 
 
126
 
127
+ # Create API-only Gradio interface
128
+ def create_api_interface():
129
+ """Create Gradio interface for API endpoints only"""
130
+
131
+ with gr.Blocks(title="AI Lead Generation API") as api_demo:
132
+ gr.Markdown("# AI Lead Generation API Server")
133
+ gr.Markdown("This server provides generic API endpoints for lead generation. All configuration is handled by the UI layer.")
134
+
135
+ with gr.Tab("Generate Report"):
136
+ gr.Markdown("### Generate Lead Report API")
137
+
138
+ with gr.Row():
139
+ with gr.Column():
140
+ api_email = gr.Textbox(
141
+ label="Email Address",
142
+ placeholder="your.email@company.com"
143
+ )
144
+ api_rss_feeds = gr.Textbox(
145
+ label="RSS Feed URLs",
146
+ lines=6,
147
+ placeholder="One URL per line"
148
+ )
149
+ api_system_prompt = gr.Textbox(
150
+ label="AI System Prompt",
151
+ lines=10,
152
+ placeholder="Enter your AI system prompt"
153
+ )
154
+ api_preferences = gr.Textbox(
155
+ label="Targeting Preferences",
156
+ lines=6,
157
+ placeholder="Enter your targeting preferences"
158
+ )
159
+
160
+ with gr.Column():
161
+ api_generate_btn = gr.Button("Generate Report", variant="primary")
162
+ api_result = gr.JSON(label="API Response")
163
+
164
+ api_generate_btn.click(
165
+ generate_lead_report,
166
+ inputs=[api_email, api_rss_feeds, api_system_prompt, api_preferences],
167
+ outputs=[api_result]
168
+ )
169
+
170
+ with gr.Tab("Server Info"):
171
+ gr.Markdown("### Server Information")
172
+
173
+ with gr.Row():
174
+ with gr.Column():
175
+ info_btn = gr.Button("Get Server Info", variant="secondary")
176
+ info_result = gr.JSON(label="Server Information")
177
+
178
+ with gr.Column():
179
+ val_email = gr.Textbox(label="Email", placeholder="test@example.com")
180
+ val_rss = gr.Textbox(label="RSS Feeds", lines=3, placeholder="Enter RSS URLs")
181
+ val_prompt = gr.Textbox(label="System Prompt", lines=5, placeholder="Enter system prompt")
182
+ val_pref = gr.Textbox(label="Preferences", lines=3, placeholder="Enter preferences")
183
+ validate_btn = gr.Button("Validate Configuration", variant="secondary")
184
+ validation_result = gr.JSON(label="Validation Result")
185
+
186
+ info_btn.click(
187
+ get_server_info,
188
+ outputs=[info_result]
189
+ )
190
+
191
+ validate_btn.click(
192
+ validate_configuration,
193
+ inputs=[val_email, val_rss, val_prompt, val_pref],
194
+ outputs=[validation_result]
195
+ )
196
+
197
+ with gr.Tab("API Documentation"):
198
+ gr.Markdown("""
199
+ ## API Endpoints
200
+
201
+ ### 1. Generate Lead Report
202
+ **Endpoint:** `/api/v1/generate`
203
+ **Method:** POST
204
+ **Parameters:**
205
+ - `email`: Valid email address for report delivery
206
+ - `rss_feeds`: RSS feed URLs (one per line)
207
+ - `system_prompt`: AI system prompt for lead analysis
208
+ - `preferences`: User targeting preferences
209
+
210
+ **Response:**
211
+ ```json
212
+ {
213
+ "success": true/false,
214
+ "result": "Generated report text",
215
+ "message": "Status message",
216
+ "email": "recipient@email.com",
217
+ "sources_count": 3,
218
+ "error": "Error message if failed"
219
+ }
220
+ ```
221
+
222
+ ### 2. Get Default Configuration
223
+ **Endpoint:** `/api/v1/config`
224
+ **Method:** GET
225
+
226
+ **Response:**
227
+ ```json
228
+ {
229
+ "success": true,
230
+ "config": {
231
+ "default_rss_feeds": "RSS feeds string",
232
+ "default_preferences": "Preferences string",
233
+ "default_prompt": "System prompt"
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### 3. Validate Configuration
239
+ **Endpoint:** `/api/v1/validate`
240
+ **Method:** POST
241
+ **Parameters:** Same as generate endpoint
242
+
243
+ **Response:**
244
+ ```json
245
+ {
246
+ "success": true,
247
+ "validation": {
248
+ "email_valid": true/false,
249
+ "rss_valid": true/false,
250
+ "prompt_valid": true/false,
251
+ "preferences_valid": true/false,
252
+ "all_valid": true/false
253
+ },
254
+ "rss_count": 3
255
+ }
256
+ ```
257
+
258
+ ## Usage with Material UI Frontend
259
+
260
+ The Material UI frontend at `/ai-agent-ui/` will make requests to these endpoints:
261
+
262
+ 1. **Load Configuration:** GET `/api/v1/config`
263
+ 2. **Validate Form:** POST `/api/v1/validate`
264
+ 3. **Generate Report:** POST `/api/v1/generate`
265
+
266
+ ## CORS Headers
267
+
268
+ The API server includes CORS headers to allow requests from the Material UI frontend.
269
+
270
+ ## Error Handling
271
+
272
+ All endpoints return consistent JSON responses with `success` boolean and appropriate error messages.
273
+ """)
274
+
275
+ return api_demo
276
 
277
  if __name__ == "__main__":
278
+ import uvicorn
279
+
280
+ print("🚀 Launching AI Lead Generation API Server...")
281
+
282
+ # Create Gradio interface
283
+ demo = create_api_interface()
284
+
285
+ # Create FastAPI app
286
+ app = FastAPI(title="AI Lead Generation API", version="1.0.0")
287
+
288
+ # Add CORS middleware
289
+ print("📡 Configuring server with CORS support...")
290
+ app.add_middleware(
291
+ CORSMiddleware,
292
+ allow_origins=["*"],
293
+ allow_credentials=True, # Enable credentials for Gradio client
294
+ allow_methods=["*"],
295
+ allow_headers=["*"],
296
+ )
297
+
298
+ # Mount the frontend directory
299
+ app.mount("/ai-agent-ui", StaticFiles(directory="ai-agent-ui", html=True), name="frontend")
300
+
301
+ # Mount Gradio app to FastAPI
302
+ app = gr.mount_gradio_app(app, demo, path="/")
303
+
304
+ print("✅ CORS configured successfully")
305
+ print("🌐 Server will be available at: http://localhost:7860")
306
+ print("🎨 Frontend available at: http://localhost:7860/ai-agent-ui/index.html")
307
+ print("📋 API documentation at: http://localhost:7860/docs")
308
+ print("🎯 Starting server with: python app_api.py")
309
+
310
+ # Launch server directly from app_api.py
311
+ uvicorn.run(app, host="0.0.0.0", port=7860)
get_tools_working.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Recruitment Lead Generation Agent
4
+
5
+ An AI-powered agent that generates high-quality leads for recruitment agencies by monitoring
6
+ funding announcements and other hiring signals using the strands framework.
7
+
8
+ This agent implements an ETL (Extract, Transform, Load) pipeline as a workflow:
9
+ 1. Extract: Fetch RSS feeds from multiple sources
10
+ 2. Transform: Use LLM to analyze articles and score leads
11
+ 3. Load: Generate CSV report and log summary information
12
+ """
13
+
14
+ import os
15
+ import json
16
+ import pandas as pd
17
+ from datetime import datetime
18
+ import requests
19
+ import sys
20
+ import xml.etree.ElementTree as ET
21
+ from botocore.config import Config as BotocoreConfig
22
+
23
+ from strands import Agent, tool
24
+ from strands.models import BedrockModel
25
+ from strands_tools import workflow
26
+
27
+ from sendgrid import SendGridAPIClient
28
+ from sendgrid.helpers.mail import Mail
29
+
30
+ # Allow full tool use without prompting
31
+ os.environ.setdefault("BYPASS_TOOL_CONSENT", "true")
32
+
33
+ # Define tool functions first
34
+
35
+ def fetch_rss(url: str = "https://techfundingnews.com/feed") -> str:
36
+ """
37
+ Extract: Load RSS feed as raw XML from the specified URL.
38
+
39
+ Args:
40
+ url: The URL of the RSS feed to fetch (default: techfundingnews.com/feed)
41
+
42
+ Returns:
43
+ The raw XML content of the RSS feed
44
+ """
45
+ try:
46
+ print(f"Fetching RSS feed from {url}")
47
+ # Add headers to mimic a browser request
48
+ headers = {
49
+ 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
50
+ 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8',
51
+ 'Accept-Language': 'en-US,en;q=0.9',
52
+ 'Referer': 'https://techfundingnews.com/'
53
+ }
54
+ response = requests.get(url, headers=headers)
55
+ print(f"Response status code: {response.status_code}")
56
+
57
+ if response.status_code != 200:
58
+ print(f"Error fetching RSS feed: {response.status_code}")
59
+ print(f"Response content: {response.text[:500]}...") # Print first 500 chars of response
60
+ return ""
61
+
62
+ print(f"Successfully fetched RSS feed, content length: {len(response.text)} characters")
63
+
64
+ # Count the number of <item> tags to see how many entries are in the feed
65
+ item_count = response.text.count("<item>")
66
+ print(f"Number of <item> tags found in RSS feed: {item_count}")
67
+
68
+ return response.text
69
+ except Exception as e:
70
+ print(f"Exception fetching RSS feed: {str(e)}")
71
+ return ""
72
+
73
+
74
+ def parse_rss(xml: str):
75
+ """
76
+ Parse RSS feed XML using Python's built-in XML parser.
77
+
78
+ Args:
79
+ xml: The raw XML content of the RSS feed
80
+
81
+ Returns:
82
+ A list of dictionaries with title, link, pubDate, summary
83
+ """
84
+ import re # Import re module here
85
+
86
+ if not xml:
87
+ print("No XML content to parse")
88
+ return []
89
+
90
+ print("Parsing RSS feed with Python XML parser")
91
+ try:
92
+ # Parse the XML
93
+ root = ET.fromstring(xml)
94
+
95
+ # Find all item elements
96
+ items = root.findall(".//item")
97
+ print(f"Found {len(items)} items in RSS feed")
98
+
99
+ entries = []
100
+ for item in items:
101
+ # Extract the title, link, pubDate, and description
102
+ title = item.find("title").text if item.find("title") is not None else ""
103
+ link = item.find("link").text if item.find("link") is not None else ""
104
+ pub_date = item.find("pubDate").text if item.find("pubDate") is not None else ""
105
+
106
+ # For the summary, try description first, then content:encoded
107
+ summary = ""
108
+ if item.find("description") is not None:
109
+ summary = item.find("description").text
110
+ elif item.find("{http://purl.org/rss/1.0/modules/content/}encoded") is not None:
111
+ summary = item.find("{http://purl.org/rss/1.0/modules/content/}encoded").text
112
+
113
+ # Clean up the summary (remove HTML tags)
114
+ summary = re.sub(r'<[^>]+>', '', summary) if summary else ""
115
+ summary = summary.strip()
116
+
117
+ # Create the entry
118
+ entry = {
119
+ "title": title,
120
+ "link": link,
121
+ "pubDate": pub_date,
122
+ "summary": summary[:500] # Limit summary length
123
+ }
124
+
125
+ entries.append(entry)
126
+
127
+ print(f"Successfully parsed {len(entries)} entries from RSS feed")
128
+ return entries
129
+ except Exception as e:
130
+ print(f"Error parsing RSS feed: {str(e)}")
131
+ print(f"Exception type: {type(e).__name__}")
132
+ print(f"Traceback: {sys.exc_info()}")
133
+ return []
134
+
135
+
136
+ @tool
137
+ def fetch_and_parse_feeds(feed_urls: list, max_entries_per_feed: int = 4):
138
+ """Tool function to fetch and parse multiple RSS feeds. Use this tool to fetch RSS content for given list of feed urls.
139
+
140
+ Args:
141
+ feed_urls: List of RSS feed URLs to fetch and parse (required)
142
+ max_entries_per_feed: Maximum number of entries to return per feed (default: 4)
143
+
144
+ Returns:
145
+ A list of entries from all feeds
146
+ """
147
+ # Ensure feed_urls is provided and is a list
148
+ if not feed_urls or not isinstance(feed_urls, list):
149
+ print("Error: feed_urls parameter is required and must be a list")
150
+ return []
151
+
152
+ # Process all feeds and collect entries
153
+ all_entries = []
154
+ for url in feed_urls:
155
+ # Extract domain name for logging
156
+ domain = url.split("//")[-1].split("/")[0]
157
+ print(f"\n--- Processing {domain} feed ---")
158
+
159
+ xml = fetch_rss(url)
160
+ if not xml:
161
+ print(f"Failed to fetch {domain} feed.")
162
+ continue
163
+
164
+ entries = parse_rss(xml)
165
+ if entries:
166
+ # Convert pubDate strings to datetime objects for sorting
167
+ from datetime import datetime
168
+ import email.utils
169
+
170
+ for entry in entries:
171
+ if 'pubDate' in entry and entry['pubDate']:
172
+ try:
173
+ # Parse the RFC 822 date format
174
+ time_tuple = email.utils.parsedate_tz(entry['pubDate'])
175
+ if time_tuple:
176
+ dt = datetime(*time_tuple[:6])
177
+ entry['_datetime'] = dt # Add datetime object for sorting
178
+ except Exception as e:
179
+ print(f"Error parsing date: {e}")
180
+ entry['_datetime'] = datetime(1970, 1, 1) # Default to epoch if parsing fails
181
+ else:
182
+ entry['_datetime'] = datetime(1970, 1, 1) # Default to epoch if no date
183
+
184
+ # Sort entries by date (newest first)
185
+ sorted_entries = sorted(entries, key=lambda x: x.get('_datetime', datetime(1970, 1, 1)), reverse=True)
186
+
187
+ # Remove the temporary _datetime field
188
+ for entry in sorted_entries:
189
+ if '_datetime' in entry:
190
+ del entry['_datetime']
191
+
192
+ # Limit the number of entries per feed
193
+ limited_entries = sorted_entries[:max_entries_per_feed]
194
+
195
+ # Add source information to each entry
196
+ for entry in limited_entries:
197
+ entry["source"] = url
198
+ entry["feed"] = domain
199
+
200
+ print(f"Found {len(entries)} entries from {domain}, using {len(limited_entries)} most recent")
201
+ all_entries.extend(limited_entries)
202
+ else:
203
+ print(f"No entries found in {domain} feed.")
204
+
205
+ print(f"\nTotal entries collected from all sources: {len(all_entries)}")
206
+ return all_entries
207
+
208
+
209
+ @tool
210
+ def send_email(to: str, html_content: str, subject: str) -> str:
211
+ """Send an email to given address"""
212
+
213
+ print(f"Sending to {to} content {html_content} and subject {subject}.")
214
+
215
+ # Get SendGrid API key from environment variable
216
+ sendgrid_api_key = os.getenv("SENDGRID_API_KEY")
217
+ if not sendgrid_api_key:
218
+ raise ValueError("SENDGRID_API_KEY environment variable is not set")
219
+
220
+ message = Mail(
221
+ from_email='kavukcu.tolga@gmail.com',
222
+ to_emails=to,
223
+ subject=subject,
224
+ html_content=html_content
225
+ )
226
+
227
+ try:
228
+ sg = SendGridAPIClient(sendgrid_api_key)
229
+ # sg.set_sendgrid_data_residency("eu")
230
+ # uncomment the above line if you are sending mail using a regional EU subuser
231
+ response = sg.send(message)
232
+ print(response.status_code)
233
+ print(response.body)
234
+ print(response.headers)
235
+ return f"Mail is sent to {to}"
236
+ except Exception as e:
237
+ print(f"Error sending email: {str(e)}")
238
+ return f"Failed to send email to {to}: {str(e)}"
239
+
240
+
241
+ def get_leadgen_agent_and_prompt(user_email: str):
242
+ """
243
+ Returns a tuple of (agent, default_prompt) for use in Gradio or other interfaces.
244
+
245
+ Args:
246
+ user_email: The email address to send the report to (required - no default)
247
+ """
248
+ if not user_email:
249
+ raise ValueError("user_email is required and cannot be empty")
250
+
251
+ custom_config = BotocoreConfig(read_timeout=3600)
252
+ bedrock_model = BedrockModel(
253
+ model_id="us.amazon.nova-pro-v1:0",
254
+ temperature=0.2,
255
+ region_name="us-west-2",
256
+ boto_client_config=custom_config,
257
+ streaming=False,
258
+ )
259
+ agent = Agent(tools=[fetch_and_parse_feeds, send_email], model=bedrock_model)
260
+
261
+ default_prompt = (
262
+ "Analyze articles and score leads based on funding and hiring signals from feeds. "
263
+ "https://techfundingnews.com/feed https://feeds.feedburner.com/vcnewsdaily https://news.crunchbase.com/feed/\n"
264
+ "You are a lead scoring AI for a recruitment agency. Your task is to analyze funding news articles and score them based on their potential as recruitment leads.\n"
265
+ "\nFor each article, extract and return the following information:\n"
266
+ "- companyName: The name of the company that received funding\n"
267
+ "- fundingStage: The funding stage (e.g., Seed, Series A, Series B, etc.)\n"
268
+ "- fundingAmount: The funding amount as a number (in millions)\n"
269
+ "- hiringSignal: Boolean (true/false) indicating if there are signals the company is hiring\n"
270
+ "- News source, with link.\n"
271
+ "- score: A score from 0-100 based on the following criteria:\n"
272
+ " * Series A and B rounds score higher (70-90) than Seed rounds (40-60)\n"
273
+ " * Higher funding amounts increase the score\n"
274
+ " * Explicit mentions of hiring or team expansion increase the score\n"
275
+ " * Tech companies and startups score higher than other industries\n"
276
+ "- reasoning: A brief explanation of the score (1-2 sentences)\n"
277
+ "- status: 'Active Hiring' if hiringSignal is true, otherwise 'Pre-emptive Opportunity'\n\n"
278
+ f"When you have the data, send an email to {user_email}. Ensure the email is professionally formatted and presents a professional analysis. So apply styling and formatting to html email."
279
+ )
280
+ return agent, default_prompt
281
+
282
+
283
+ if __name__ == "__main__":
284
+ print("Initializing recruitment lead generation agent...")
285
+
286
+ # Print Python version
287
+ print(f"Python version: {sys.version}")
288
+
289
+ # Create the Bedrock model with Claude 4 Sonnet
290
+ print("Creating Bedrock model...")
291
+ # The model and prompt are now handled by get_leadgen_agent_and_prompt()
292
+ # custom_config = BotocoreConfig(read_timeout=3600)
293
+ # bedrock_model = BedrockModel(
294
+ # model_id="us.amazon.nova-pro-v1:0",
295
+ # temperature=0.2,
296
+ # region_name="us-west-2", # Explicitly set the region to us-west-2
297
+ # boto_client_config=custom_config,
298
+ # streaming=False,
299
+ # )
300
+ # print(f"Bedrock model type: {type(bedrock_model)}")
301
+
302
+ # Create the main agent
303
+ print("Creating agent...")
requirements.txt CHANGED
@@ -1 +1,10 @@
1
- huggingface_hub==0.25.2
 
 
 
 
 
 
 
 
 
 
1
+ huggingface_hub==0.25.2
2
+ gradio
3
+ strands-agents
4
+ strands-agents-tools
5
+ botocore
6
+ pandas
7
+ requests
8
+ sendgrid
9
+ uvicorn
10
+ fastapi