muzakkirhussain011 Claude commited on
Commit
a7ac06e
·
1 Parent(s): 101cb4f

Complete app redesign with proper B2B sales workflow

Browse files

Major architectural changes:
- New Setup tab: User enters THEIR company name (client)
- AI researches client company and builds knowledge base
- Knowledge base used to personalize all prospect outreach

New features:
- Client onboarding with AI research
- Prospect research on behalf of client company
- Expandable prospect cards showing full details
- Expandable email cards with full content
- Handoff Packet generation (full sales briefing document)
- AI Chat tab for prospect engagement assistance
- Setup Required warnings when client not configured

Business flow:
1. Setup: Enter your company → AI builds knowledge base
2. Prospects: Research target companies
3. Emails: View AI-drafted personalized outreach
4. Handoff: Generate comprehensive sales packets
5. AI Chat: Get help with talking points, objections, etc.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>

Files changed (1) hide show
  1. app.py +1022 -847
app.py CHANGED
@@ -1,8 +1,15 @@
1
  """
2
  CX AI Agent - Enterprise B2B Sales Intelligence Platform
3
 
4
- A modern, user-friendly interface for AI-powered sales automation.
5
- Designed for sales teams, not developers.
 
 
 
 
 
 
 
6
  """
7
 
8
  import os
@@ -69,12 +76,26 @@ except Exception as e:
69
  print(f"❌ Initialization failed: {e}")
70
  raise
71
 
72
- # Global storage for session data
73
- session_data = {
74
- "prospects": [],
75
- "emails": [],
76
- "contacts": [],
77
- "research_count": 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  }
79
 
80
 
@@ -84,7 +105,6 @@ session_data = {
84
  ENTERPRISE_CSS = """
85
  /* ===== ENTERPRISE THEME - LIGHT/DARK MODE COMPATIBLE ===== */
86
 
87
- /* Light mode variables (default) */
88
  :root, .light {
89
  --primary-blue: #0176D3;
90
  --primary-dark: #014486;
@@ -96,34 +116,23 @@ ENTERPRISE_CSS = """
96
  --error-red: #EA001E;
97
  --error-light: #FDE7E9;
98
  --purple: #9050E9;
99
-
100
- /* Backgrounds */
101
  --bg-primary: #FFFFFF;
102
  --bg-secondary: #FAFBFC;
103
  --bg-tertiary: #F3F3F3;
104
  --bg-hover: #F8FBFE;
105
-
106
- /* Text colors */
107
  --text-primary: #181818;
108
  --text-secondary: #706E6B;
109
  --text-tertiary: #969492;
110
  --text-inverse: #FFFFFF;
111
-
112
- /* Borders */
113
  --border-color: #E5E5E5;
114
  --border-hover: #0176D3;
115
-
116
- /* Shadows */
117
  --card-shadow: 0 2px 8px rgba(0,0,0,0.08);
118
  --card-shadow-hover: 0 4px 16px rgba(0,0,0,0.12);
119
-
120
- /* Inputs */
121
  --input-bg: #FFFFFF;
122
  --input-border: #E5E5E5;
123
  --input-focus-shadow: rgba(1, 118, 211, 0.1);
124
  }
125
 
126
- /* Dark mode variables */
127
  .dark {
128
  --primary-blue: #4BA3E3;
129
  --primary-dark: #2D8FD5;
@@ -135,34 +144,23 @@ ENTERPRISE_CSS = """
135
  --error-red: #EF5350;
136
  --error-light: #3D2020;
137
  --purple: #B794F6;
138
-
139
- /* Backgrounds */
140
  --bg-primary: #1E1E1E;
141
  --bg-secondary: #252525;
142
  --bg-tertiary: #2D2D2D;
143
  --bg-hover: #333333;
144
-
145
- /* Text colors */
146
  --text-primary: #E8E8E8;
147
  --text-secondary: #A8A8A8;
148
  --text-tertiary: #888888;
149
  --text-inverse: #1E1E1E;
150
-
151
- /* Borders */
152
  --border-color: #404040;
153
  --border-hover: #4BA3E3;
154
-
155
- /* Shadows */
156
  --card-shadow: 0 2px 8px rgba(0,0,0,0.3);
157
  --card-shadow-hover: 0 4px 16px rgba(0,0,0,0.4);
158
-
159
- /* Inputs */
160
  --input-bg: #2D2D2D;
161
  --input-border: #404040;
162
  --input-focus-shadow: rgba(75, 163, 227, 0.2);
163
  }
164
 
165
- /* System preference detection */
166
  @media (prefers-color-scheme: dark) {
167
  :root:not(.light) {
168
  --primary-blue: #4BA3E3;
@@ -201,13 +199,13 @@ ENTERPRISE_CSS = """
201
  background: var(--bg-secondary) !important;
202
  }
203
 
204
- /* Header/Banner styling */
205
  .main-header {
206
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-dark) 100%);
207
  color: var(--text-inverse);
208
- padding: 28px 32px;
209
  border-radius: 16px;
210
- margin-bottom: 24px;
211
  box-shadow: var(--card-shadow);
212
  display: flex;
213
  align-items: center;
@@ -215,47 +213,44 @@ ENTERPRISE_CSS = """
215
  }
216
 
217
  .header-logo {
218
- width: 60px;
219
- height: 60px;
220
  background: rgba(255,255,255,0.2);
221
  border-radius: 12px;
222
  display: flex;
223
  align-items: center;
224
  justify-content: center;
225
- font-size: 32px;
226
  }
227
 
228
- .header-content {
229
- flex: 1;
230
- }
 
231
 
232
- .header-content h1 {
233
- margin: 0 0 6px 0;
234
- font-size: 28px;
235
- font-weight: 700;
236
- color: white !important;
 
 
237
  display: flex;
238
  align-items: center;
239
- gap: 10px;
240
- }
241
-
242
- .header-content p {
243
- margin: 0;
244
- opacity: 0.9;
245
- font-size: 15px;
246
- color: rgba(255,255,255,0.9) !important;
247
  }
248
 
249
- .header-badge {
250
- background: rgba(255,255,255,0.2);
251
- padding: 6px 14px;
252
- border-radius: 20px;
253
- font-size: 12px;
254
- font-weight: 600;
255
- color: white;
 
 
256
  }
257
 
258
- /* Stats cards */
259
  .stat-card {
260
  background: var(--bg-primary);
261
  border-radius: 12px;
@@ -263,27 +258,11 @@ ENTERPRISE_CSS = """
263
  box-shadow: var(--card-shadow);
264
  border-left: 4px solid var(--primary-blue);
265
  transition: all 0.2s ease;
266
- height: 100%;
267
  }
268
 
269
- .stat-card:hover {
270
- box-shadow: var(--card-shadow-hover);
271
- transform: translateY(-2px);
272
- }
273
-
274
- .stat-card .stat-value {
275
- font-size: 28px;
276
- font-weight: 700;
277
- color: var(--text-primary);
278
- margin-bottom: 4px;
279
- }
280
-
281
- .stat-card .stat-label {
282
- font-size: 13px;
283
- color: var(--text-secondary);
284
- text-transform: uppercase;
285
- letter-spacing: 0.5px;
286
- }
287
 
288
  /* Action cards */
289
  .action-card {
@@ -295,38 +274,11 @@ ENTERPRISE_CSS = """
295
  border: 1px solid var(--border-color);
296
  }
297
 
298
- .action-card h3 {
299
- margin: 0 0 12px 0;
300
- color: var(--text-primary);
301
- font-size: 18px;
302
- font-weight: 600;
303
- }
304
-
305
- .action-card p {
306
- margin: 0 0 16px 0;
307
- color: var(--text-secondary);
308
- font-size: 14px;
309
- line-height: 1.6;
310
- }
311
-
312
- .action-card ul, .action-card ol {
313
- color: var(--text-secondary);
314
- margin: 0;
315
- padding-left: 20px;
316
- }
317
-
318
- .action-card li {
319
- color: var(--text-secondary);
320
- margin-bottom: 8px;
321
- line-height: 1.5;
322
- }
323
-
324
- .action-card strong {
325
- color: var(--text-primary);
326
- }
327
 
328
  /* Primary button */
329
- .primary-btn, button.primary {
330
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-dark) 100%) !important;
331
  color: white !important;
332
  border: none !important;
@@ -340,12 +292,8 @@ ENTERPRISE_CSS = """
340
  min-height: 44px !important;
341
  }
342
 
343
- .primary-btn:hover, button.primary:hover {
344
- transform: translateY(-1px) !important;
345
- box-shadow: 0 4px 12px rgba(1, 118, 211, 0.4) !important;
346
- }
347
 
348
- /* Secondary button */
349
  button.secondary {
350
  background: var(--bg-primary) !important;
351
  color: var(--primary-blue) !important;
@@ -357,24 +305,14 @@ button.secondary {
357
  min-height: 36px !important;
358
  }
359
 
360
- button.secondary:hover {
361
- background: var(--primary-light) !important;
362
- }
363
 
364
- /* Stop button */
365
- .stop-btn {
366
  background: var(--error-red) !important;
367
  color: white !important;
368
  border: none !important;
369
  }
370
 
371
- /* Clear button */
372
- .clear-btn {
373
- background: var(--bg-tertiary) !important;
374
- color: var(--text-secondary) !important;
375
- border: 1px solid var(--border-color) !important;
376
- }
377
-
378
  /* Input fields */
379
  input[type="text"], textarea {
380
  background: var(--input-bg) !important;
@@ -392,114 +330,115 @@ input[type="text"]:focus, textarea:focus {
392
  outline: none !important;
393
  }
394
 
395
- /* Results panel */
396
- .results-panel {
397
  background: var(--bg-primary);
398
  border-radius: 12px;
399
- padding: 24px;
400
  border: 1px solid var(--border-color);
401
- min-height: 400px;
 
402
  }
403
 
404
- .results-panel h3 {
405
- margin: 0 0 16px 0;
406
- color: var(--text-primary);
 
 
 
 
 
 
 
 
 
407
  font-size: 16px;
408
  font-weight: 600;
 
 
 
 
409
  }
410
 
411
- /* Tabs styling */
412
- .tab-nav button {
413
- border: none !important;
414
- background: var(--bg-primary) !important;
415
- padding: 14px 24px !important;
416
- font-weight: 600 !important;
417
- font-size: 14px !important;
418
- color: var(--text-secondary) !important;
419
- border-radius: 8px 8px 0 0 !important;
420
- margin-right: 4px !important;
421
- transition: all 0.2s ease !important;
422
  }
423
 
424
- .tab-nav button:hover {
425
- background: var(--bg-hover) !important;
426
- color: var(--text-primary) !important;
 
 
 
 
 
427
  }
428
 
429
- .tab-nav button.selected {
430
- background: var(--primary-blue) !important;
431
- color: white !important;
432
  }
433
 
434
- /* Quick action tiles - clickable buttons */
435
- .quick-action-btn {
436
- background: var(--bg-primary) !important;
437
- border: 2px solid var(--border-color) !important;
438
- border-radius: 12px !important;
439
- padding: 24px 16px !important;
440
- text-align: center !important;
441
- cursor: pointer !important;
442
- transition: all 0.2s ease !important;
443
- min-height: 120px !important;
444
- display: flex !important;
445
- flex-direction: column !important;
446
- align-items: center !important;
447
- justify-content: center !important;
448
- gap: 8px !important;
449
  }
450
 
451
- .quick-action-btn:hover {
452
- border-color: var(--primary-blue) !important;
453
- background: var(--bg-hover) !important;
454
- transform: translateY(-2px) !important;
455
- box-shadow: var(--card-shadow-hover) !important;
456
  }
457
 
458
- /* Prospect/Email cards */
459
- .data-card {
460
  background: var(--bg-primary);
461
  border-radius: 12px;
462
- padding: 20px;
463
- margin-bottom: 12px;
464
  border: 1px solid var(--border-color);
465
- box-shadow: var(--card-shadow);
466
- }
467
-
468
- .data-card-header {
469
  display: flex;
470
- justify-content: space-between;
471
- align-items: center;
472
- margin-bottom: 12px;
473
- padding-bottom: 12px;
474
- border-bottom: 1px solid var(--border-color);
475
  }
476
 
477
- .data-card-title {
478
- font-size: 16px;
479
- font-weight: 600;
480
- color: var(--text-primary);
481
  }
482
 
483
- .data-card-badge {
484
- padding: 4px 12px;
485
- border-radius: 12px;
486
- font-size: 12px;
487
- font-weight: 600;
488
  }
489
 
490
- .badge-new {
491
- background: var(--primary-light);
492
- color: var(--primary-blue);
 
 
 
 
 
493
  }
494
 
495
- .badge-contacted {
496
- background: var(--warning-light);
497
- color: var(--warning-orange);
 
498
  }
499
 
500
- .badge-qualified {
501
- background: var(--success-light);
502
- color: var(--success-green);
 
503
  }
504
 
505
  /* Empty state */
@@ -509,24 +448,9 @@ input[type="text"]:focus, textarea:focus {
509
  color: var(--text-secondary);
510
  }
511
 
512
- .empty-state-icon {
513
- font-size: 56px;
514
- margin-bottom: 16px;
515
- opacity: 0.6;
516
- }
517
-
518
- .empty-state-title {
519
- font-size: 18px;
520
- font-weight: 600;
521
- color: var(--text-primary);
522
- margin-bottom: 8px;
523
- }
524
-
525
- .empty-state-desc {
526
- font-size: 14px;
527
- color: var(--text-secondary);
528
- margin-bottom: 20px;
529
- }
530
 
531
  /* Footer */
532
  .footer {
@@ -537,208 +461,426 @@ input[type="text"]:focus, textarea:focus {
537
  margin-top: 32px;
538
  }
539
 
540
- .footer p {
541
- margin: 0;
542
- color: var(--text-secondary);
543
- }
544
-
545
- /* Section headers */
546
- .section-header {
547
- display: flex;
548
- justify-content: space-between;
549
- align-items: center;
550
- margin-bottom: 16px;
551
- padding: 16px 0;
552
- }
553
-
554
- .section-title {
555
- font-size: 20px;
556
- font-weight: 600;
557
- color: var(--text-primary);
558
- margin: 0;
559
  }
560
 
561
- .section-subtitle {
562
- font-size: 14px;
563
- color: var(--text-secondary);
564
- margin: 4px 0 0 0;
565
- }
566
 
567
- /* Loading animation */
568
- @keyframes pulse {
569
- 0%, 100% { opacity: 1; }
570
- 50% { opacity: 0.5; }
571
- }
572
 
573
- .loading {
574
- animation: pulse 1.5s ease-in-out infinite;
575
- }
 
576
 
577
  /* Responsive */
578
  @media (max-width: 768px) {
579
- .main-header {
580
- flex-direction: column;
581
- text-align: center;
582
- padding: 20px;
583
- }
584
-
585
- .header-content h1 {
586
- font-size: 22px;
587
- }
588
  }
 
589
 
590
- /* Dark mode overrides for Gradio components */
591
- .dark input, .dark textarea, .dark select {
592
- background: var(--input-bg) !important;
593
- color: var(--text-primary) !important;
594
- border-color: var(--input-border) !important;
595
- }
596
 
597
- .dark label {
598
- color: var(--text-primary) !important;
599
- }
600
 
601
- .dark .prose, .dark .prose p, .dark .prose li {
602
- color: var(--text-primary) !important;
603
- }
 
 
 
 
 
604
 
605
- .dark .prose strong {
606
- color: var(--text-primary) !important;
607
- }
608
 
609
- .dark .prose h1, .dark .prose h2, .dark .prose h3, .dark .prose h4 {
610
- color: var(--text-primary) !important;
611
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
612
 
613
- .dark .prose table th, .dark .prose table td {
614
- border-color: var(--border-color) !important;
615
- color: var(--text-primary) !important;
616
- }
617
 
618
- .dark details summary {
619
- color: var(--text-primary) !important;
620
- }
 
 
621
 
622
- /* Reset/Stop button styling */
623
- button.stop {
624
- background: var(--error-red) !important;
625
- color: white !important;
626
- border: none !important;
627
- }
628
 
629
- button.stop:hover {
630
- background: #c91b1b !important;
631
- }
632
 
633
- /* Better radio button styling */
634
- .gradio-radio label {
635
- padding: 12px 16px !important;
636
- border-radius: 8px !important;
637
- border: 2px solid var(--border-color) !important;
638
- margin-bottom: 8px !important;
639
- transition: all 0.2s ease !important;
640
- cursor: pointer !important;
641
- }
 
642
 
643
- .gradio-radio label:hover {
644
- border-color: var(--primary-blue) !important;
645
- background: var(--bg-hover) !important;
646
- }
 
 
 
 
 
 
 
 
 
 
 
647
 
648
- .gradio-radio input:checked + label {
649
- border-color: var(--primary-blue) !important;
650
- background: var(--primary-light) !important;
651
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
652
 
653
- /* Progress bar styling */
654
- .progress-bar {
655
- background: var(--primary-light) !important;
656
- border-radius: 8px !important;
657
- }
 
 
658
 
659
- .progress-bar > div {
660
- background: linear-gradient(90deg, var(--primary-blue), var(--primary-dark)) !important;
661
- border-radius: 8px !important;
662
- }
 
 
663
 
664
- /* Smooth transitions for all interactive elements */
665
- button, input, .data-card, .stat-card, .action-card {
666
- transition: all 0.2s ease !important;
667
- }
 
 
 
 
 
 
 
 
 
668
 
669
- /* Better markdown rendering */
670
- .prose {
671
- max-width: none !important;
672
- }
673
 
674
- .prose code {
675
- background: var(--bg-tertiary) !important;
676
- padding: 2px 6px !important;
677
- border-radius: 4px !important;
678
- font-size: 13px !important;
679
- }
680
 
681
- .prose pre {
682
- background: var(--bg-tertiary) !important;
683
- border-radius: 8px !important;
684
- padding: 16px !important;
685
- overflow-x: auto !important;
686
- }
 
 
 
 
687
 
688
- .prose pre code {
689
- background: transparent !important;
690
- padding: 0 !important;
691
- }
692
 
693
- /* Status indicator animation */
694
- @keyframes statusPulse {
695
- 0%, 100% { box-shadow: 0 0 0 0 rgba(1, 118, 211, 0.4); }
696
- 50% { box-shadow: 0 0 0 8px rgba(1, 118, 211, 0); }
697
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
 
699
- .status-active {
700
- animation: statusPulse 2s infinite;
701
- }
702
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
703
 
704
 
705
  # ============================================================================
706
- # AGENT EXECUTION
707
  # ============================================================================
708
- async def run_sales_agent(company_name: str, task_type: str, progress=gr.Progress()):
709
- """Run the AI agent with user-friendly output formatting."""
710
- global session_data
711
 
712
  if not company_name or not company_name.strip():
713
- yield format_error("Please enter a company name to research.")
714
  return
715
 
 
 
716
  hf_token = os.getenv('HF_TOKEN') or os.getenv('HF_API_TOKEN')
717
  if not hf_token:
718
- yield format_error("AI services not configured. Please contact your administrator.")
719
  return
720
 
721
- company_name = company_name.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
722
 
723
- # Build task based on type
724
- task_templates = {
725
- "full_research": f"Research {company_name} thoroughly: find company information, identify 3-5 key decision makers (CEO, VP Sales, CTO), save company profile and contacts, then draft a personalized outreach email to the primary contact.",
726
- "company_profile": f"Research {company_name} and create a detailed company profile including industry, size, recent news, and key business insights. Save all information to the database.",
727
- "find_contacts": f"Find decision makers at {company_name} including CEO, VP of Sales, CTO, and other executives. Save their contact information with names, titles, and any available LinkedIn profiles.",
728
- "draft_email": f"Research {company_name}, understand their business challenges, and draft a personalized B2B sales outreach email that addresses their specific pain points.",
729
- "competitive_intel": f"Research {company_name} and gather competitive intelligence including their products, market position, recent news, funding, and potential pain points we could address."
 
 
 
 
 
 
 
 
 
 
 
730
  }
731
 
732
- task = task_templates.get(task_type, task_templates["full_research"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
733
 
734
- provider = os.getenv('HF_PROVIDER', 'nscale')
735
- model = os.getenv('HF_MODEL', 'Qwen/Qwen3-4B-Instruct-2507')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
 
737
- # User-friendly output
738
  output = f"""
739
- ## 🔍 Researching: {company_name}
740
 
741
- **Task:** {get_task_label(task_type)}
742
 
743
  ---
744
 
@@ -746,342 +888,347 @@ async def run_sales_agent(company_name: str, task_type: str, progress=gr.Progres
746
 
747
  """
748
  yield output
749
- progress(0.1, desc="Starting research...")
750
 
751
  try:
752
  agent = AutonomousMCPAgentHF(
753
  mcp_registry=mcp_registry,
754
  hf_token=hf_token,
755
- provider=provider,
756
- model=model
757
  )
758
-
759
  output += "✅ AI Agent initialized\n\n"
760
  yield output
761
  progress(0.2, desc="Agent ready...")
762
-
763
  except Exception as e:
764
- logger.error(f"Agent initialization failed: {e}")
765
- yield format_error(f"Could not start AI agent. Please try again later.")
766
  return
767
 
768
- # Track results for summary
769
- results = {
770
- "company_saved": False,
771
- "contacts_found": 0,
772
- "facts_gathered": 0,
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  "email_drafted": False,
774
- "email_content": "",
775
- "email_subject": "",
776
- "email_to": "",
777
- "steps_completed": []
 
 
 
 
 
 
778
  }
779
 
780
  try:
781
  iteration = 0
782
- async for event in agent.run(task, max_iterations=15):
783
  event_type = event.get("type")
784
  iteration += 1
785
-
786
  progress_pct = min(0.2 + (iteration * 0.05), 0.9)
787
 
788
  if event_type == "tool_call":
789
  tool = event.get("tool", "")
790
  tool_input = event.get("input", {})
791
 
792
- # Capture email content from send_email tool call
793
- if tool == "send_email" and isinstance(tool_input, dict):
794
- results["email_content"] = tool_input.get("body", "")
795
- results["email_subject"] = tool_input.get("subject", f"Introduction - {company_name}")
796
- results["email_to"] = tool_input.get("to", "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
 
798
- step_desc = get_friendly_step(tool, tool_input, company_name)
799
- if step_desc:
800
- output += f"🔄 {step_desc}\n"
801
- results["steps_completed"].append(step_desc)
802
- yield output
803
- progress(progress_pct, desc=step_desc[:50])
804
 
805
  elif event_type == "tool_result":
806
  tool = event.get("tool", "")
807
  result = event.get("result", {})
808
 
809
- if tool == "save_company":
810
- results["company_saved"] = True
811
- output += " ✅ Company profile saved\n"
812
- elif tool == "save_contact":
813
- results["contacts_found"] += 1
814
- output += f" ✅ Contact #{results['contacts_found']} saved\n"
815
- elif tool == "save_fact":
816
- results["facts_gathered"] += 1
817
- elif tool == "save_prospect":
818
- output += " ✅ Prospect record created\n"
819
- # Add to session data
820
- session_data["prospects"].append({
821
- "company": company_name,
822
- "status": "new",
823
- "date": datetime.now().strftime("%Y-%m-%d %H:%M"),
824
- "contacts": results["contacts_found"]
825
- })
826
- elif tool in ["search_web", "search_news"]:
827
  count = result.get("count", 0) if isinstance(result, dict) else 0
828
  output += f" ✅ Found {count} results\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
  elif tool == "send_email":
830
- results["email_drafted"] = True
831
  output += " ✅ Email drafted\n"
832
- # Add to session emails with full content
833
- session_data["emails"].append({
834
- "company": company_name,
835
- "date": datetime.now().strftime("%Y-%m-%d %H:%M"),
836
- "subject": results["email_subject"] or f"Introduction - {company_name}",
837
- "to": results["email_to"],
838
- "body": results["email_content"],
839
- "status": "draft"
840
- })
841
 
842
  yield output
843
 
844
- elif event_type == "tool_error":
845
- pass # Don't show errors to users
846
-
847
  elif event_type == "agent_complete":
848
  final_answer = event.get("final_answer", "")
849
- session_data["research_count"] += 1
 
 
 
 
 
 
850
 
851
  output += "\n---\n\n"
852
- output += "## ✅ Research Complete!\n\n"
853
- output += format_results_summary(results, company_name)
854
- output += "\n### 📋 Detailed Findings\n\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  output += final_answer
856
 
857
  yield output
858
  progress(1.0, desc="Complete!")
859
-
860
- elif event_type == "agent_error":
861
- output += f"\n⚠️ Encountered an issue, but partial results may be available.\n"
862
- yield output
863
-
864
- elif event_type == "agent_max_iterations":
865
- output += "\n---\n\n"
866
- output += "## ⏱️ Research Summary\n\n"
867
- output += format_results_summary(results, company_name)
868
- output += "\n*Research time limit reached. Results above are partial.*\n"
869
- yield output
870
 
871
  except Exception as e:
872
- logger.error(f"Agent execution error: {e}")
873
- output += f"\n\n⚠️ Research interrupted. Partial results may be available above.\n"
874
  yield output
875
 
876
 
877
- def get_task_label(task_type: str) -> str:
878
- """Get user-friendly task label."""
879
- labels = {
880
- "full_research": "Complete prospect research with contacts and email",
881
- "company_profile": "Company profile and insights",
882
- "find_contacts": "Find decision makers",
883
- "draft_email": "Draft personalized outreach email",
884
- "competitive_intel": "Competitive intelligence gathering"
885
- }
886
- return labels.get(task_type, "Research task")
887
-
888
-
889
- def get_friendly_step(tool: str, tool_input: dict, company: str) -> str:
890
- """Convert technical tool names to user-friendly descriptions."""
891
- friendly = {
892
- "search_web": f"Searching for {company} information...",
893
- "search_news": f"Finding recent news about {company}...",
894
- "save_company": f"Saving {company} company profile...",
895
- "save_prospect": f"Creating prospect record for {company}...",
896
- "save_contact": f"Saving contact: {tool_input.get('name', 'decision maker')}...",
897
- "save_fact": "Recording business insight...",
898
- "get_company": f"Retrieving {company} data...",
899
- "get_prospect": "Loading prospect information...",
900
- "list_prospects": "Checking existing prospects...",
901
- "send_email": "Drafting outreach email...",
902
- "get_thread": "Loading email thread...",
903
- "suggest_slots": "Finding available meeting times...",
904
- "generate_ics": "Creating calendar invite..."
905
- }
906
- return friendly.get(tool, "")
907
-
908
-
909
- def format_results_summary(results: dict, company: str) -> str:
910
- """Format a user-friendly results summary."""
911
- summary = f"### 📊 Results for {company}\n\n"
912
- summary += "| Metric | Status |\n"
913
- summary += "|--------|--------|\n"
914
- summary += f"| Company Profile | {'✅ Saved' if results['company_saved'] else '⏳ Pending'} |\n"
915
- summary += f"| Contacts Found | {results['contacts_found']} decision makers |\n"
916
- summary += f"| Business Insights | {results['facts_gathered']} facts gathered |\n"
917
- summary += f"| Outreach Email | {'✅ Drafted' if results['email_drafted'] else '⏳ Pending'} |\n\n"
918
-
919
- # Add email content if drafted
920
- if results['email_drafted'] and results.get('email_content'):
921
- summary += "---\n\n"
922
- summary += "### ✉️ Drafted Outreach Email\n\n"
923
- if results.get('email_to'):
924
- summary += f"**To:** {results['email_to']}\n\n"
925
- if results.get('email_subject'):
926
- summary += f"**Subject:** {results['email_subject']}\n\n"
927
- summary += "**Email Body:**\n\n"
928
- summary += f"```\n{results['email_content']}\n```\n\n"
929
- summary += "*💡 Tip: Copy this email and personalize it before sending. You can also view all drafted emails in the Emails tab.*\n\n"
930
-
931
- return summary
932
-
933
-
934
- def format_error(message: str) -> str:
935
- """Format user-friendly error message."""
936
- return f"""
937
- ## ⚠️ Unable to Complete Request
938
 
939
- {message}
 
 
940
 
941
- **Need help?** Try:
942
- - Checking your internet connection
943
- - Using a different company name
944
- - Refreshing the page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
945
 
946
- If the problem persists, please try again in a few minutes.
947
  """
948
 
 
 
 
 
 
949
 
950
- def clear_results():
951
- """Clear research results."""
952
- return "*Enter a company name and click 'Start Research' to begin.*"
953
 
 
954
 
955
- def reset_all_data():
956
- """Reset all session data."""
957
- global session_data
958
- session_data = {
959
- "prospects": [],
960
- "emails": [],
961
- "contacts": [],
962
- "research_count": 0
963
- }
964
- return (
965
- get_stat_html("0", "Prospects Researched", "var(--primary-blue)"),
966
- get_stat_html("0", "Emails Drafted", "var(--success-green)"),
967
- get_stat_html("0", "Contacts Found", "var(--warning-orange)"),
968
- get_prospects_html(),
969
- get_emails_html(),
970
- "*All data has been reset. Enter a company name to start fresh.*"
971
- )
972
 
 
 
 
 
 
973
 
974
- def get_stat_html(value: str, label: str, color: str) -> str:
975
- """Generate HTML for a stat card."""
976
- return f"""
977
- <div class="stat-card" style="border-left-color: {color};">
978
- <div class="stat-value">{value}</div>
979
- <div class="stat-label">{label}</div>
980
- </div>
981
- """
982
 
 
983
 
984
- def get_dashboard_stats():
985
- """Get current dashboard statistics as HTML."""
986
- prospects_count = len(session_data["prospects"])
987
- emails_count = len(session_data["emails"])
988
- contacts_count = sum(p.get("contacts", 0) for p in session_data["prospects"])
989
 
990
- return (
991
- get_stat_html(str(prospects_count), "Prospects Researched", "var(--primary-blue)"),
992
- get_stat_html(str(emails_count), "Emails Drafted", "var(--success-green)"),
993
- get_stat_html(str(contacts_count), "Contacts Found", "var(--warning-orange)")
994
- )
995
 
 
996
 
997
- def get_prospects_html():
998
- """Generate HTML for prospects list."""
999
- if not session_data["prospects"]:
1000
- return """
1001
- <div class="empty-state">
1002
- <div class="empty-state-icon">📁</div>
1003
- <div class="empty-state-title">No prospects yet</div>
1004
- <div class="empty-state-desc">Research your first company to get started</div>
1005
- </div>
1006
- """
1007
 
1008
- html = ""
1009
- for p in reversed(session_data["prospects"][-10:]): # Show last 10
1010
- badge_class = "badge-new" if p["status"] == "new" else "badge-contacted"
1011
- html += f"""
1012
- <div class="data-card">
1013
- <div class="data-card-header">
1014
- <span class="data-card-title">🏢 {p['company']}</span>
1015
- <span class="data-card-badge {badge_class}">{p['status'].upper()}</span>
1016
- </div>
1017
- <div style="color: var(--text-secondary); font-size: 13px;">
1018
- <p style="margin: 4px 0;">📅 Researched: {p['date']}</p>
1019
- <p style="margin: 4px 0;">👥 Contacts: {p.get('contacts', 0)}</p>
1020
- </div>
1021
- </div>
1022
- """
1023
- return html
1024
 
 
 
 
1025
 
1026
- def get_emails_html():
1027
- """Generate HTML for emails list."""
1028
- if not session_data["emails"]:
1029
- return """
1030
- <div class="empty-state">
1031
- <div class="empty-state-icon">✉️</div>
1032
- <div class="empty-state-title">No emails drafted yet</div>
1033
- <div class="empty-state-desc">Research a company with email drafting to get started</div>
1034
- </div>
1035
- """
1036
 
1037
- html = ""
1038
- for e in reversed(session_data["emails"][-10:]): # Show last 10
1039
- # Escape HTML in body and format for display
1040
- body = e.get('body', '')
1041
- if body:
1042
- # Convert newlines to <br> and escape HTML
1043
- body_display = body.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('\n', '<br>')
1044
- body_preview = body[:200] + "..." if len(body) > 200 else body
1045
- body_preview = body_preview.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('\n', ' ')
1046
- else:
1047
- body_display = "<em>No content available</em>"
1048
- body_preview = "No preview available"
1049
-
1050
- to_addr = e.get('to', 'Not specified')
1051
- subject = e.get('subject', 'No subject')
1052
 
1053
- html += f"""
1054
- <div class="data-card">
1055
- <div class="data-card-header">
1056
- <span class="data-card-title">✉️ {subject[:60]}{'...' if len(subject) > 60 else ''}</span>
1057
- <span class="data-card-badge badge-new">{e['status'].upper()}</span>
1058
- </div>
1059
- <div style="color: var(--text-secondary); font-size: 13px; margin-bottom: 12px;">
1060
- <p style="margin: 4px 0;">🏢 Company: <strong>{e['company']}</strong></p>
1061
- <p style="margin: 4px 0;">📧 To: {to_addr}</p>
1062
- <p style="margin: 4px 0;">📅 Created: {e['date']}</p>
1063
- </div>
1064
- <details style="margin-top: 12px; border-top: 1px solid var(--border-color); padding-top: 12px;">
1065
- <summary style="cursor: pointer; font-weight: 600; color: var(--primary-blue); padding: 4px 0;">
1066
- 📄 View Full Email Content
1067
- </summary>
1068
- <div style="margin-top: 12px; padding: 16px; background: var(--bg-secondary); border-radius: 8px; border: 1px solid var(--border-color);">
1069
- <p style="margin: 0 0 8px 0; font-weight: 600; color: var(--text-primary);">Subject: {subject}</p>
1070
- <p style="margin: 0 0 12px 0; color: var(--text-secondary);">To: {to_addr}</p>
1071
- <hr style="border: none; border-top: 1px solid var(--border-color); margin: 12px 0;">
1072
- <div style="color: var(--text-primary); line-height: 1.6; white-space: pre-wrap;">{body_display}</div>
1073
- </div>
1074
- </details>
1075
- </div>
1076
- """
1077
- return html
 
1078
 
1079
 
1080
  # ============================================================================
1081
- # GRADIO UI - ENTERPRISE DESIGN
1082
  # ============================================================================
1083
- def create_enterprise_ui():
1084
- """Create enterprise-grade Gradio interface."""
1085
 
1086
  with gr.Blocks(
1087
  title="CX AI Agent - B2B Sales Intelligence",
@@ -1094,7 +1241,7 @@ def create_enterprise_ui():
1094
  css=ENTERPRISE_CSS
1095
  ) as demo:
1096
 
1097
- # ===== HEADER/BANNER =====
1098
  gr.HTML("""
1099
  <div class="main-header">
1100
  <div class="header-logo">
@@ -1112,28 +1259,68 @@ def create_enterprise_ui():
1112
  </div>
1113
  """)
1114
 
1115
- # ===== MAIN TABS =====
1116
  with gr.Tabs() as tabs:
1117
 
1118
- # ===== TAB 1: DASHBOARD =====
1119
- with gr.Tab("📊 Dashboard", id=0):
1120
- # Section header with refresh and reset
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1121
  with gr.Row():
1122
  with gr.Column(scale=4):
1123
  gr.HTML("""
1124
  <div style="padding: 8px 0;">
1125
  <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Sales Command Center</h2>
1126
- <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Real-time overview of your AI-powered sales pipeline</p>
1127
  </div>
1128
  """)
1129
  with gr.Column(scale=1):
1130
- with gr.Row():
1131
- refresh_dashboard_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
1132
- reset_all_btn = gr.Button("🗑️ Reset All", variant="stop", size="sm")
1133
 
1134
- gr.HTML("<div style='height: 16px;'></div>")
1135
-
1136
- # Quick Stats Row - Dynamic
1137
  with gr.Row():
1138
  prospects_stat = gr.HTML(get_stat_html("0", "Prospects Researched", "var(--primary-blue)"))
1139
  emails_stat = gr.HTML(get_stat_html("0", "Emails Drafted", "var(--success-green)"))
@@ -1147,261 +1334,170 @@ def create_enterprise_ui():
1147
 
1148
  gr.HTML("<div style='height: 24px;'></div>")
1149
 
1150
- # Quick Actions
1151
- gr.HTML("""
1152
- <h3 style="margin: 0 0 16px 0; color: var(--text-primary); font-size: 18px; font-weight: 600;">⚡ Quick Actions</h3>
1153
- """)
1154
-
1155
- with gr.Row():
1156
- qa_research = gr.Button("🔍 Research Company\nStart prospect research", elem_classes=["quick-action-btn"])
1157
- qa_contacts = gr.Button("👥 Find Contacts\nDiscover decision makers", elem_classes=["quick-action-btn"])
1158
- qa_email = gr.Button("✉️ Draft Email\nAI-written outreach", elem_classes=["quick-action-btn"])
1159
- qa_pipeline = gr.Button("📈 View Pipeline\nManage prospects", elem_classes=["quick-action-btn"])
1160
-
1161
- gr.HTML("<div style='height: 24px;'></div>")
1162
-
1163
- # Two-column layout for getting started and recent activity
1164
  with gr.Row():
1165
- with gr.Column(scale=1):
1166
  gr.HTML("""
1167
  <div class="action-card">
1168
- <h3>🎯 How It Works</h3>
1169
  <ol style="margin: 0; padding-left: 20px;">
1170
- <li style="margin-bottom: 12px;"><strong>Enter Company Name</strong><br><span style="color: var(--text-secondary); font-size: 13px;">Type any company you want to research</span></li>
1171
- <li style="margin-bottom: 12px;"><strong>AI Research</strong><br><span style="color: var(--text-secondary); font-size: 13px;">Our AI gathers company info, news, and contacts</span></li>
1172
- <li style="margin-bottom: 12px;"><strong>Get Personalized Email</strong><br><span style="color: var(--text-secondary); font-size: 13px;">Receive a tailored outreach email</span></li>
1173
- <li><strong>Export & Send</strong><br><span style="color: var(--text-secondary); font-size: 13px;">Copy the email to your CRM or email client</span></li>
1174
  </ol>
1175
  </div>
1176
  """)
1177
- with gr.Column(scale=1):
1178
  gr.HTML("""
1179
  <div class="action-card">
1180
- <h3>💡 Pro Tips</h3>
1181
  <ul style="margin: 0; padding-left: 20px;">
1182
- <li style="margin-bottom: 10px;">Use <strong>official company names</strong> for better results</li>
1183
- <li style="margin-bottom: 10px;">Try <strong>"Full Research"</strong> for comprehensive data</li>
1184
- <li style="margin-bottom: 10px;">Research takes <strong>1-2 minutes</strong> - be patient</li>
1185
- <li style="margin-bottom: 10px;"><strong>Review emails</strong> before sending - AI is a starting point</li>
1186
- <li>Click <strong>"View Pipeline"</strong> to see all researched companies</li>
1187
  </ul>
1188
  </div>
1189
  """)
1190
 
1191
- # ===== TAB 2: RESEARCH =====
1192
- with gr.Tab("🔍 Research", id=1):
 
 
1193
  gr.HTML("""
1194
  <div style="padding: 8px 0 16px 0;">
1195
  <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Prospect Research</h2>
1196
- <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Enter a company name to start AI-powered research</p>
1197
  </div>
1198
  """)
1199
 
1200
  with gr.Row():
1201
- # Left Panel - Input
1202
  with gr.Column(scale=1):
1203
- with gr.Group():
1204
- gr.HTML("<h3 style='margin: 0 0 16px 0; color: var(--text-primary);'>🏢 Company Information</h3>")
1205
-
1206
- company_input = gr.Textbox(
1207
- label="Company Name",
1208
- placeholder="Enter company name (e.g., Salesforce, HubSpot, Stripe)",
1209
- lines=1,
1210
- max_lines=1
1211
- )
1212
-
1213
- task_type = gr.Radio(
1214
- label="What would you like to do?",
1215
- choices=[
1216
- ("🔍 Full Research (Recommended)", "full_research"),
1217
- ("🏢 Company Profile Only", "company_profile"),
1218
- ("👥 Find Decision Makers", "find_contacts"),
1219
- ("✉️ Draft Outreach Email", "draft_email"),
1220
- ("📊 Competitive Intelligence", "competitive_intel")
1221
- ],
1222
- value="full_research"
1223
- )
1224
-
1225
- with gr.Row():
1226
- research_btn = gr.Button("🚀 Start Research", variant="primary", size="lg")
1227
- clear_btn = gr.Button("🗑️ Clear", variant="secondary", size="lg")
1228
 
1229
- gr.HTML("<div style='height: 16px;'></div>")
 
 
 
 
1230
 
1231
- with gr.Group():
1232
- gr.HTML("<h3 style='margin: 0 0 12px 0; color: var(--text-primary);'>💡 Quick Examples</h3>")
1233
- gr.HTML("<p style='margin: 0 0 12px 0; color: var(--text-secondary); font-size: 13px;'>Click a company to auto-fill:</p>")
1234
 
1235
- with gr.Row():
1236
- ex1 = gr.Button("Salesforce", size="sm", variant="secondary")
1237
- ex2 = gr.Button("HubSpot", size="sm", variant="secondary")
1238
- ex3 = gr.Button("Stripe", size="sm", variant="secondary")
1239
- with gr.Row():
1240
- ex4 = gr.Button("Shopify", size="sm", variant="secondary")
1241
- ex5 = gr.Button("Zendesk", size="sm", variant="secondary")
1242
- ex6 = gr.Button("Slack", size="sm", variant="secondary")
1243
 
1244
- # Right Panel - Results
1245
- with gr.Column(scale=2):
1246
- gr.HTML("<h3 style='margin: 0 0 16px 0; color: var(--text-primary);'>📋 Research Results</h3>")
 
 
1247
 
 
1248
  research_output = gr.Markdown(
1249
- value="*Enter a company name and click 'Start Research' to begin.*\n\n**Tip:** For best results, use the official company name (e.g., \"Salesforce\" instead of \"SF\")."
1250
  )
1251
 
1252
- # Wire up buttons
1253
- research_btn.click(
1254
- fn=run_sales_agent,
1255
- inputs=[company_input, task_type],
1256
- outputs=[research_output]
1257
- )
1258
-
1259
- clear_btn.click(
1260
- fn=clear_results,
1261
- outputs=[research_output]
1262
- )
1263
 
1264
- # Example buttons
1265
- ex1.click(lambda: "Salesforce", outputs=company_input)
1266
- ex2.click(lambda: "HubSpot", outputs=company_input)
1267
- ex3.click(lambda: "Stripe", outputs=company_input)
1268
- ex4.click(lambda: "Shopify", outputs=company_input)
1269
- ex5.click(lambda: "Zendesk", outputs=company_input)
1270
- ex6.click(lambda: "Slack", outputs=company_input)
1271
-
1272
- # ===== TAB 3: PROSPECTS =====
1273
- with gr.Tab("👥 Prospects", id=2):
1274
  with gr.Row():
1275
- with gr.Column(scale=4):
1276
- gr.HTML("""
1277
- <div style="padding: 8px 0;">
1278
- <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Prospect Pipeline</h2>
1279
- <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Companies you've researched and their status</p>
1280
- </div>
1281
- """)
1282
- with gr.Column(scale=1):
1283
- refresh_prospects_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
1284
-
1285
- gr.HTML("<div style='height: 16px;'></div>")
1286
 
1287
  prospects_list = gr.HTML(get_prospects_html())
1288
 
1289
- refresh_prospects_btn.click(
1290
- fn=get_prospects_html,
1291
- outputs=[prospects_list]
1292
- )
1293
 
1294
- # ===== TAB 4: EMAILS =====
1295
  with gr.Tab("✉️ Emails", id=3):
1296
- with gr.Row():
1297
- with gr.Column(scale=4):
1298
- gr.HTML("""
1299
- <div style="padding: 8px 0;">
1300
- <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Email Drafts</h2>
1301
- <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">AI-generated outreach emails ready for review and sending</p>
1302
- </div>
1303
- """)
1304
- with gr.Column(scale=1):
1305
- refresh_emails_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
1306
 
1307
- gr.HTML("<div style='height: 16px;'></div>")
 
1308
 
1309
  emails_list = gr.HTML(get_emails_html())
1310
 
1311
- refresh_emails_btn.click(
1312
- fn=get_emails_html,
1313
- outputs=[emails_list]
1314
- )
1315
-
1316
- # ===== TAB 5: HELP =====
1317
- with gr.Tab("❓ Help", id=4):
1318
  gr.HTML("""
1319
- <div class="section-header">
1320
- <div>
1321
- <h2 class="section-title">Help & Documentation</h2>
1322
- <p class="section-subtitle">Learn how to use CX AI Agent effectively</p>
1323
- </div>
1324
  </div>
1325
  """)
1326
 
1327
  with gr.Row():
1328
- with gr.Column():
1329
  gr.HTML("""
1330
  <div class="action-card">
1331
- <h3>🎯 What is CX AI Agent?</h3>
1332
- <p>CX AI Agent is an AI-powered B2B sales intelligence platform that helps your team:</p>
1333
- <ul>
1334
- <li><strong>Research Companies</strong> — Automatically gather company information, news, and insights</li>
1335
- <li><strong>Find Decision Makers</strong> — Identify CEOs, VPs, and other key contacts</li>
1336
- <li><strong>Draft Personalized Emails</strong> — Generate outreach that addresses specific pain points</li>
1337
- <li><strong>Save Time</strong> — What takes hours manually is done in minutes</li>
1338
- </ul>
1339
- </div>
1340
-
1341
- <div class="action-card">
1342
- <h3>🚀 How to Research a Company</h3>
1343
- <ol>
1344
- <li>Go to the <strong>Research</strong> tab</li>
1345
- <li>Enter a company name (e.g., "Salesforce")</li>
1346
- <li>Select what you want to do (Full Research is recommended)</li>
1347
- <li>Click <strong>Start Research</strong></li>
1348
- <li>Wait 1-2 minutes for AI to complete the research</li>
1349
- </ol>
1350
  </div>
1351
  """)
1352
 
1353
- with gr.Column():
1354
- gr.HTML("""
1355
- <div class="action-card">
1356
- <h3>💡 Tips for Best Results</h3>
1357
- <ul>
1358
- <li><strong>Use official company names</strong> — "Salesforce" works better than "SF"</li>
1359
- <li><strong>Start with Full Research</strong> — Gets you the most comprehensive results</li>
1360
- <li><strong>Be patient</strong> — AI research takes 1-2 minutes to complete</li>
1361
- <li><strong>Review AI outputs</strong> — Always verify before sending emails</li>
1362
- </ul>
1363
- </div>
1364
 
1365
- <div class="action-card">
1366
- <h3>❓ Frequently Asked Questions</h3>
1367
- <details style="margin-bottom: 12px;">
1368
- <summary style="cursor: pointer; font-weight: 600; padding: 8px 0;">How accurate is the research?</summary>
1369
- <p style="margin: 8px 0 0 0; padding-left: 12px;">AI searches the web in real-time for current information. However, always verify critical details before using in outreach.</p>
1370
- </details>
1371
- <details style="margin-bottom: 12px;">
1372
- <summary style="cursor: pointer; font-weight: 600; padding: 8px 0;">How long does research take?</summary>
1373
- <p style="margin: 8px 0 0 0; padding-left: 12px;">Most research completes in 1-2 minutes depending on complexity.</p>
1374
- </details>
1375
- <details style="margin-bottom: 12px;">
1376
- <summary style="cursor: pointer; font-weight: 600; padding: 8px 0;">Can I research multiple companies?</summary>
1377
- <p style="margin: 8px 0 0 0; padding-left: 12px;">Currently, research one company at a time for best results.</p>
1378
- </details>
1379
- <details>
1380
- <summary style="cursor: pointer; font-weight: 600; padding: 8px 0;">Is my data secure?</summary>
1381
- <p style="margin: 8px 0 0 0; padding-left: 12px;">Yes, all data is processed securely and not shared with third parties.</p>
1382
- </details>
1383
- </div>
1384
- """)
1385
 
1386
- # Quick action button handlers - switch to appropriate tabs
1387
- qa_research.click(lambda: gr.Tabs(selected=1), outputs=[tabs])
1388
- qa_contacts.click(lambda: gr.Tabs(selected=1), outputs=[tabs])
1389
- qa_email.click(lambda: gr.Tabs(selected=1), outputs=[tabs])
1390
- qa_pipeline.click(lambda: gr.Tabs(selected=2), outputs=[tabs])
1391
 
1392
- # Dashboard refresh - update all stats
1393
- refresh_dashboard_btn.click(
1394
- fn=get_dashboard_stats,
1395
- outputs=[prospects_stat, emails_stat, contacts_stat]
1396
- )
 
 
 
1397
 
1398
- # Reset all data
1399
- reset_all_btn.click(
1400
- fn=reset_all_data,
1401
- outputs=[prospects_stat, emails_stat, contacts_stat, prospects_list, emails_list, research_output]
1402
- )
 
 
 
 
 
 
 
 
 
1403
 
1404
- # ===== FOOTER =====
 
 
 
 
 
 
 
 
 
 
 
 
1405
  gr.HTML("""
1406
  <div class="footer">
1407
  <p style="font-size: 14px; margin-bottom: 4px;"><strong>CX AI Agent</strong> — Enterprise B2B Sales Intelligence</p>
@@ -1409,11 +1505,90 @@ def create_enterprise_ui():
1409
  </div>
1410
  """)
1411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1412
  return demo
1413
 
1414
 
1415
  if __name__ == "__main__":
1416
- demo = create_enterprise_ui()
1417
  demo.launch(
1418
  server_name="0.0.0.0",
1419
  server_port=7860,
 
1
  """
2
  CX AI Agent - Enterprise B2B Sales Intelligence Platform
3
 
4
+ A comprehensive AI-powered sales platform that:
5
+ 1. Onboards client companies and builds their knowledge base
6
+ 2. Researches prospect companies in depth
7
+ 3. Finds decision makers and contacts
8
+ 4. Drafts personalized outreach emails
9
+ 5. Generates handoff packets for sales teams
10
+ 6. Provides AI chat for prospect engagement
11
+
12
+ Designed for B2B sales teams to accelerate their pipeline.
13
  """
14
 
15
  import os
 
76
  print(f"❌ Initialization failed: {e}")
77
  raise
78
 
79
+
80
+ # ============================================================================
81
+ # KNOWLEDGE BASE - Session Storage
82
+ # ============================================================================
83
+ knowledge_base = {
84
+ "client": {
85
+ "name": None,
86
+ "profile": None,
87
+ "products_services": [],
88
+ "value_proposition": None,
89
+ "target_market": None,
90
+ "competitive_advantages": [],
91
+ "researched_at": None,
92
+ "raw_research": None
93
+ },
94
+ "prospects": [], # List of researched prospect companies
95
+ "contacts": [], # Decision makers found
96
+ "emails": [], # Drafted emails
97
+ "chat_history": [], # AI chat conversation history
98
+ "handoff_packets": [] # Generated handoff documents
99
  }
100
 
101
 
 
105
  ENTERPRISE_CSS = """
106
  /* ===== ENTERPRISE THEME - LIGHT/DARK MODE COMPATIBLE ===== */
107
 
 
108
  :root, .light {
109
  --primary-blue: #0176D3;
110
  --primary-dark: #014486;
 
116
  --error-red: #EA001E;
117
  --error-light: #FDE7E9;
118
  --purple: #9050E9;
 
 
119
  --bg-primary: #FFFFFF;
120
  --bg-secondary: #FAFBFC;
121
  --bg-tertiary: #F3F3F3;
122
  --bg-hover: #F8FBFE;
 
 
123
  --text-primary: #181818;
124
  --text-secondary: #706E6B;
125
  --text-tertiary: #969492;
126
  --text-inverse: #FFFFFF;
 
 
127
  --border-color: #E5E5E5;
128
  --border-hover: #0176D3;
 
 
129
  --card-shadow: 0 2px 8px rgba(0,0,0,0.08);
130
  --card-shadow-hover: 0 4px 16px rgba(0,0,0,0.12);
 
 
131
  --input-bg: #FFFFFF;
132
  --input-border: #E5E5E5;
133
  --input-focus-shadow: rgba(1, 118, 211, 0.1);
134
  }
135
 
 
136
  .dark {
137
  --primary-blue: #4BA3E3;
138
  --primary-dark: #2D8FD5;
 
144
  --error-red: #EF5350;
145
  --error-light: #3D2020;
146
  --purple: #B794F6;
 
 
147
  --bg-primary: #1E1E1E;
148
  --bg-secondary: #252525;
149
  --bg-tertiary: #2D2D2D;
150
  --bg-hover: #333333;
 
 
151
  --text-primary: #E8E8E8;
152
  --text-secondary: #A8A8A8;
153
  --text-tertiary: #888888;
154
  --text-inverse: #1E1E1E;
 
 
155
  --border-color: #404040;
156
  --border-hover: #4BA3E3;
 
 
157
  --card-shadow: 0 2px 8px rgba(0,0,0,0.3);
158
  --card-shadow-hover: 0 4px 16px rgba(0,0,0,0.4);
 
 
159
  --input-bg: #2D2D2D;
160
  --input-border: #404040;
161
  --input-focus-shadow: rgba(75, 163, 227, 0.2);
162
  }
163
 
 
164
  @media (prefers-color-scheme: dark) {
165
  :root:not(.light) {
166
  --primary-blue: #4BA3E3;
 
199
  background: var(--bg-secondary) !important;
200
  }
201
 
202
+ /* Header */
203
  .main-header {
204
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-dark) 100%);
205
  color: var(--text-inverse);
206
+ padding: 24px 32px;
207
  border-radius: 16px;
208
+ margin-bottom: 20px;
209
  box-shadow: var(--card-shadow);
210
  display: flex;
211
  align-items: center;
 
213
  }
214
 
215
  .header-logo {
216
+ width: 56px;
217
+ height: 56px;
218
  background: rgba(255,255,255,0.2);
219
  border-radius: 12px;
220
  display: flex;
221
  align-items: center;
222
  justify-content: center;
 
223
  }
224
 
225
+ .header-content { flex: 1; }
226
+ .header-content h1 { margin: 0 0 4px 0; font-size: 26px; font-weight: 700; color: white !important; }
227
+ .header-content p { margin: 0; opacity: 0.9; font-size: 14px; color: rgba(255,255,255,0.9) !important; }
228
+ .header-badge { background: rgba(255,255,255,0.2); padding: 6px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; color: white; }
229
 
230
+ /* Setup required banner */
231
+ .setup-required {
232
+ background: var(--warning-light);
233
+ border: 2px solid var(--warning-orange);
234
+ border-radius: 12px;
235
+ padding: 16px 20px;
236
+ margin-bottom: 20px;
237
  display: flex;
238
  align-items: center;
239
+ gap: 12px;
 
 
 
 
 
 
 
240
  }
241
 
242
+ .setup-complete {
243
+ background: var(--success-light);
244
+ border: 2px solid var(--success-green);
245
+ border-radius: 12px;
246
+ padding: 16px 20px;
247
+ margin-bottom: 20px;
248
+ display: flex;
249
+ align-items: center;
250
+ gap: 12px;
251
  }
252
 
253
+ /* Stat cards */
254
  .stat-card {
255
  background: var(--bg-primary);
256
  border-radius: 12px;
 
258
  box-shadow: var(--card-shadow);
259
  border-left: 4px solid var(--primary-blue);
260
  transition: all 0.2s ease;
 
261
  }
262
 
263
+ .stat-card:hover { box-shadow: var(--card-shadow-hover); transform: translateY(-2px); }
264
+ .stat-card .stat-value { font-size: 28px; font-weight: 700; color: var(--text-primary); margin-bottom: 4px; }
265
+ .stat-card .stat-label { font-size: 13px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
  /* Action cards */
268
  .action-card {
 
274
  border: 1px solid var(--border-color);
275
  }
276
 
277
+ .action-card h3 { margin: 0 0 12px 0; color: var(--text-primary); font-size: 18px; font-weight: 600; }
278
+ .action-card p { margin: 0 0 16px 0; color: var(--text-secondary); font-size: 14px; line-height: 1.6; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
  /* Primary button */
281
+ button.primary {
282
  background: linear-gradient(135deg, var(--primary-blue) 0%, var(--primary-dark) 100%) !important;
283
  color: white !important;
284
  border: none !important;
 
292
  min-height: 44px !important;
293
  }
294
 
295
+ button.primary:hover { transform: translateY(-1px) !important; box-shadow: 0 4px 12px rgba(1, 118, 211, 0.4) !important; }
 
 
 
296
 
 
297
  button.secondary {
298
  background: var(--bg-primary) !important;
299
  color: var(--primary-blue) !important;
 
305
  min-height: 36px !important;
306
  }
307
 
308
+ button.secondary:hover { background: var(--primary-light) !important; }
 
 
309
 
310
+ button.stop {
 
311
  background: var(--error-red) !important;
312
  color: white !important;
313
  border: none !important;
314
  }
315
 
 
 
 
 
 
 
 
316
  /* Input fields */
317
  input[type="text"], textarea {
318
  background: var(--input-bg) !important;
 
330
  outline: none !important;
331
  }
332
 
333
+ /* Prospect/Data cards - Expandable */
334
+ .prospect-card {
335
  background: var(--bg-primary);
336
  border-radius: 12px;
337
+ margin-bottom: 12px;
338
  border: 1px solid var(--border-color);
339
+ box-shadow: var(--card-shadow);
340
+ overflow: hidden;
341
  }
342
 
343
+ .prospect-card-header {
344
+ padding: 16px 20px;
345
+ display: flex;
346
+ justify-content: space-between;
347
+ align-items: center;
348
+ cursor: pointer;
349
+ transition: background 0.2s ease;
350
+ }
351
+
352
+ .prospect-card-header:hover { background: var(--bg-hover); }
353
+
354
+ .prospect-card-title {
355
  font-size: 16px;
356
  font-weight: 600;
357
+ color: var(--text-primary);
358
+ display: flex;
359
+ align-items: center;
360
+ gap: 10px;
361
  }
362
 
363
+ .prospect-card-badge {
364
+ padding: 4px 12px;
365
+ border-radius: 12px;
366
+ font-size: 12px;
367
+ font-weight: 600;
 
 
 
 
 
 
368
  }
369
 
370
+ .badge-new { background: var(--primary-light); color: var(--primary-blue); }
371
+ .badge-researched { background: var(--success-light); color: var(--success-green); }
372
+ .badge-contacted { background: var(--warning-light); color: var(--warning-orange); }
373
+
374
+ .prospect-card-details {
375
+ padding: 0 20px 20px 20px;
376
+ border-top: 1px solid var(--border-color);
377
+ background: var(--bg-secondary);
378
  }
379
 
380
+ .detail-section {
381
+ margin-top: 16px;
 
382
  }
383
 
384
+ .detail-section h4 {
385
+ font-size: 13px;
386
+ font-weight: 600;
387
+ color: var(--text-secondary);
388
+ text-transform: uppercase;
389
+ letter-spacing: 0.5px;
390
+ margin: 0 0 8px 0;
 
 
 
 
 
 
 
 
391
  }
392
 
393
+ .detail-section p, .detail-section li {
394
+ font-size: 14px;
395
+ color: var(--text-primary);
396
+ line-height: 1.6;
397
+ margin: 4px 0;
398
  }
399
 
400
+ /* Chat interface */
401
+ .chat-container {
402
  background: var(--bg-primary);
403
  border-radius: 12px;
 
 
404
  border: 1px solid var(--border-color);
405
+ height: 500px;
 
 
 
406
  display: flex;
407
+ flex-direction: column;
 
 
 
 
408
  }
409
 
410
+ .chat-messages {
411
+ flex: 1;
412
+ overflow-y: auto;
413
+ padding: 20px;
414
  }
415
 
416
+ .chat-message {
417
+ margin-bottom: 16px;
418
+ display: flex;
419
+ gap: 12px;
 
420
  }
421
 
422
+ .chat-message.user { flex-direction: row-reverse; }
423
+
424
+ .chat-bubble {
425
+ max-width: 70%;
426
+ padding: 12px 16px;
427
+ border-radius: 12px;
428
+ font-size: 14px;
429
+ line-height: 1.5;
430
  }
431
 
432
+ .chat-bubble.assistant {
433
+ background: var(--bg-tertiary);
434
+ color: var(--text-primary);
435
+ border-bottom-left-radius: 4px;
436
  }
437
 
438
+ .chat-bubble.user {
439
+ background: var(--primary-blue);
440
+ color: white;
441
+ border-bottom-right-radius: 4px;
442
  }
443
 
444
  /* Empty state */
 
448
  color: var(--text-secondary);
449
  }
450
 
451
+ .empty-state-icon { font-size: 56px; margin-bottom: 16px; opacity: 0.6; }
452
+ .empty-state-title { font-size: 18px; font-weight: 600; color: var(--text-primary); margin-bottom: 8px; }
453
+ .empty-state-desc { font-size: 14px; color: var(--text-secondary); margin-bottom: 20px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
454
 
455
  /* Footer */
456
  .footer {
 
461
  margin-top: 32px;
462
  }
463
 
464
+ /* Tabs */
465
+ .tab-nav button {
466
+ border: none !important;
467
+ background: var(--bg-primary) !important;
468
+ padding: 14px 24px !important;
469
+ font-weight: 600 !important;
470
+ font-size: 14px !important;
471
+ color: var(--text-secondary) !important;
472
+ border-radius: 8px 8px 0 0 !important;
473
+ margin-right: 4px !important;
474
+ transition: all 0.2s ease !important;
 
 
 
 
 
 
 
 
475
  }
476
 
477
+ .tab-nav button:hover { background: var(--bg-hover) !important; color: var(--text-primary) !important; }
478
+ .tab-nav button.selected { background: var(--primary-blue) !important; color: white !important; }
 
 
 
479
 
480
+ /* Markdown */
481
+ .prose { max-width: none !important; }
482
+ .prose code { background: var(--bg-tertiary) !important; padding: 2px 6px !important; border-radius: 4px !important; font-size: 13px !important; }
483
+ .prose pre { background: var(--bg-tertiary) !important; border-radius: 8px !important; padding: 16px !important; overflow-x: auto !important; }
484
+ .prose pre code { background: transparent !important; padding: 0 !important; }
485
 
486
+ /* Dark mode fixes */
487
+ .dark input, .dark textarea, .dark select { background: var(--input-bg) !important; color: var(--text-primary) !important; border-color: var(--input-border) !important; }
488
+ .dark label { color: var(--text-primary) !important; }
489
+ .dark .prose, .dark .prose p, .dark .prose li { color: var(--text-primary) !important; }
490
 
491
  /* Responsive */
492
  @media (max-width: 768px) {
493
+ .main-header { flex-direction: column; text-align: center; padding: 20px; }
494
+ .header-content h1 { font-size: 22px; }
 
 
 
 
 
 
 
495
  }
496
+ """
497
 
 
 
 
 
 
 
498
 
499
+ # ============================================================================
500
+ # HELPER FUNCTIONS
501
+ # ============================================================================
502
 
503
+ def get_stat_html(value: str, label: str, color: str) -> str:
504
+ """Generate HTML for a stat card."""
505
+ return f"""
506
+ <div class="stat-card" style="border-left-color: {color};">
507
+ <div class="stat-value">{value}</div>
508
+ <div class="stat-label">{label}</div>
509
+ </div>
510
+ """
511
 
 
 
 
512
 
513
+ def get_client_status_html() -> str:
514
+ """Get client setup status banner."""
515
+ if knowledge_base["client"]["name"]:
516
+ return f"""
517
+ <div class="setup-complete">
518
+ <span style="font-size: 24px;">✅</span>
519
+ <div>
520
+ <strong style="color: var(--success-green);">Client Profile Active</strong>
521
+ <p style="margin: 4px 0 0 0; font-size: 13px; color: var(--text-secondary);">
522
+ Researching prospects on behalf of <strong>{knowledge_base["client"]["name"]}</strong>
523
+ </p>
524
+ </div>
525
+ </div>
526
+ """
527
+ else:
528
+ return """
529
+ <div class="setup-required">
530
+ <span style="font-size: 24px;">⚠️</span>
531
+ <div>
532
+ <strong style="color: var(--warning-orange);">Setup Required</strong>
533
+ <p style="margin: 4px 0 0 0; font-size: 13px; color: var(--text-secondary);">
534
+ Please go to the <strong>Setup</strong> tab to enter your company name before researching prospects.
535
+ </p>
536
+ </div>
537
+ </div>
538
+ """
539
 
 
 
 
 
540
 
541
+ def get_dashboard_stats():
542
+ """Get current dashboard statistics."""
543
+ prospects_count = len(knowledge_base["prospects"])
544
+ emails_count = len(knowledge_base["emails"])
545
+ contacts_count = len(knowledge_base["contacts"])
546
 
547
+ return (
548
+ get_stat_html(str(prospects_count), "Prospects Researched", "var(--primary-blue)"),
549
+ get_stat_html(str(emails_count), "Emails Drafted", "var(--success-green)"),
550
+ get_stat_html(str(contacts_count), "Contacts Found", "var(--warning-orange)"),
551
+ get_client_status_html()
552
+ )
553
 
 
 
 
554
 
555
+ def get_prospects_html() -> str:
556
+ """Generate expandable prospect cards."""
557
+ if not knowledge_base["prospects"]:
558
+ return """
559
+ <div class="empty-state">
560
+ <div class="empty-state-icon">🎯</div>
561
+ <div class="empty-state-title">No prospects yet</div>
562
+ <div class="empty-state-desc">Research your first prospect company to build your pipeline</div>
563
+ </div>
564
+ """
565
 
566
+ html = ""
567
+ for i, p in enumerate(reversed(knowledge_base["prospects"])):
568
+ status_class = "badge-researched" if p.get("research_complete") else "badge-new"
569
+ status_text = "RESEARCHED" if p.get("research_complete") else "NEW"
570
+
571
+ # Build details section
572
+ details_html = ""
573
+
574
+ if p.get("summary"):
575
+ details_html += f"""
576
+ <div class="detail-section">
577
+ <h4>📋 Company Summary</h4>
578
+ <p>{p.get("summary", "No summary available")}</p>
579
+ </div>
580
+ """
581
 
582
+ if p.get("industry"):
583
+ details_html += f"""
584
+ <div class="detail-section">
585
+ <h4>🏢 Industry</h4>
586
+ <p>{p.get("industry", "Unknown")}</p>
587
+ </div>
588
+ """
589
+
590
+ if p.get("pain_points"):
591
+ pain_points_list = "".join([f"<li>{pp}</li>" for pp in p.get("pain_points", [])])
592
+ details_html += f"""
593
+ <div class="detail-section">
594
+ <h4>🎯 Pain Points Identified</h4>
595
+ <ul style="margin: 0; padding-left: 20px;">{pain_points_list}</ul>
596
+ </div>
597
+ """
598
+
599
+ if p.get("contacts"):
600
+ contacts_list = "".join([f"<li><strong>{c.get('name', 'Unknown')}</strong> - {c.get('title', 'Unknown title')}</li>" for c in p.get("contacts", [])])
601
+ details_html += f"""
602
+ <div class="detail-section">
603
+ <h4>👥 Decision Makers</h4>
604
+ <ul style="margin: 0; padding-left: 20px;">{contacts_list}</ul>
605
+ </div>
606
+ """
607
 
608
+ if p.get("email_drafted"):
609
+ details_html += f"""
610
+ <div class="detail-section">
611
+ <h4>✉️ Email Status</h4>
612
+ <p style="color: var(--success-green);">✅ Outreach email drafted</p>
613
+ </div>
614
+ """
615
 
616
+ details_html += f"""
617
+ <div class="detail-section">
618
+ <h4>📅 Researched</h4>
619
+ <p>{p.get("researched_at", "Unknown date")}</p>
620
+ </div>
621
+ """
622
 
623
+ html += f"""
624
+ <details class="prospect-card">
625
+ <summary class="prospect-card-header">
626
+ <span class="prospect-card-title">
627
+ 🏢 {p.get("name", "Unknown Company")}
628
+ </span>
629
+ <span class="prospect-card-badge {status_class}">{status_text}</span>
630
+ </summary>
631
+ <div class="prospect-card-details">
632
+ {details_html}
633
+ </div>
634
+ </details>
635
+ """
636
 
637
+ return html
 
 
 
638
 
 
 
 
 
 
 
639
 
640
+ def get_emails_html() -> str:
641
+ """Generate expandable email cards."""
642
+ if not knowledge_base["emails"]:
643
+ return """
644
+ <div class="empty-state">
645
+ <div class="empty-state-icon">✉️</div>
646
+ <div class="empty-state-title">No emails drafted yet</div>
647
+ <div class="empty-state-desc">Research a prospect to automatically generate personalized outreach emails</div>
648
+ </div>
649
+ """
650
 
651
+ html = ""
652
+ for e in reversed(knowledge_base["emails"]):
653
+ body_display = e.get("body", "").replace("\n", "<br>")
 
654
 
655
+ html += f"""
656
+ <details class="prospect-card">
657
+ <summary class="prospect-card-header">
658
+ <span class="prospect-card-title">
659
+ ✉️ {e.get("subject", "No subject")[:50]}{'...' if len(e.get("subject", "")) > 50 else ''}
660
+ </span>
661
+ <span class="prospect-card-badge badge-new">{e.get("status", "DRAFT").upper()}</span>
662
+ </summary>
663
+ <div class="prospect-card-details">
664
+ <div class="detail-section">
665
+ <h4>📧 Recipient</h4>
666
+ <p>{e.get("to", "Not specified")}</p>
667
+ </div>
668
+ <div class="detail-section">
669
+ <h4>🏢 Prospect Company</h4>
670
+ <p>{e.get("prospect_company", "Unknown")}</p>
671
+ </div>
672
+ <div class="detail-section">
673
+ <h4>📝 Subject Line</h4>
674
+ <p><strong>{e.get("subject", "No subject")}</strong></p>
675
+ </div>
676
+ <div class="detail-section">
677
+ <h4>📄 Email Body</h4>
678
+ <div style="background: var(--bg-tertiary); padding: 16px; border-radius: 8px; margin-top: 8px;">
679
+ <p style="white-space: pre-wrap; margin: 0;">{body_display}</p>
680
+ </div>
681
+ </div>
682
+ <div class="detail-section">
683
+ <h4>📅 Created</h4>
684
+ <p>{e.get("created_at", "Unknown date")}</p>
685
+ </div>
686
+ </div>
687
+ </details>
688
+ """
689
 
690
+ return html
691
+
692
+
693
+ def reset_all_data():
694
+ """Reset all session data."""
695
+ global knowledge_base
696
+ knowledge_base = {
697
+ "client": {
698
+ "name": None,
699
+ "profile": None,
700
+ "products_services": [],
701
+ "value_proposition": None,
702
+ "target_market": None,
703
+ "competitive_advantages": [],
704
+ "researched_at": None,
705
+ "raw_research": None
706
+ },
707
+ "prospects": [],
708
+ "contacts": [],
709
+ "emails": [],
710
+ "chat_history": [],
711
+ "handoff_packets": []
712
+ }
713
+ stats = get_dashboard_stats()
714
+ return (
715
+ stats[0], stats[1], stats[2], stats[3],
716
+ get_prospects_html(),
717
+ get_emails_html(),
718
+ "", # Clear client name input
719
+ "*Setup required. Enter your company name above.*",
720
+ "*Enter a prospect company name and click 'Research Prospect' to begin.*"
721
+ )
722
 
723
 
724
  # ============================================================================
725
+ # CLIENT SETUP - Research the user's company
726
  # ============================================================================
727
+ async def setup_client_company(company_name: str, progress=gr.Progress()):
728
+ """Research and setup the client (user's) company profile."""
729
+ global knowledge_base
730
 
731
  if not company_name or not company_name.strip():
732
+ yield "⚠️ Please enter your company name."
733
  return
734
 
735
+ company_name = company_name.strip()
736
+
737
  hf_token = os.getenv('HF_TOKEN') or os.getenv('HF_API_TOKEN')
738
  if not hf_token:
739
+ yield "AI services not configured. Please contact administrator."
740
  return
741
 
742
+ output = f"""
743
+ ## 🏢 Setting Up: {company_name}
744
+
745
+ Building your company's knowledge base...
746
+
747
+ ---
748
+
749
+ ### ⏳ Progress
750
+
751
+ """
752
+ yield output
753
+ progress(0.1, desc="Initializing...")
754
+
755
+ try:
756
+ agent = AutonomousMCPAgentHF(
757
+ mcp_registry=mcp_registry,
758
+ hf_token=hf_token,
759
+ provider=os.getenv('HF_PROVIDER', 'nscale'),
760
+ model=os.getenv('HF_MODEL', 'Qwen/Qwen3-4B-Instruct-2507')
761
+ )
762
+ output += "✅ AI Agent initialized\n\n"
763
+ yield output
764
+ progress(0.2, desc="Agent ready...")
765
+ except Exception as e:
766
+ logger.error(f"Agent init failed: {e}")
767
+ yield f"❌ Could not start AI agent: {e}"
768
+ return
769
 
770
+ task = f"""Research {company_name} thoroughly to build a comprehensive company profile. Find:
771
+ 1. Company overview, mission, and what they do
772
+ 2. Products and services they offer
773
+ 3. Their target market and ideal customers
774
+ 4. Value proposition and competitive advantages
775
+ 5. Recent news, funding, or important developments
776
+ 6. Industry and market position
777
+
778
+ Save all findings to the knowledge base. This is OUR company - we will use this info to personalize outreach to prospects."""
779
+
780
+ research_data = {
781
+ "name": company_name,
782
+ "summary": "",
783
+ "products_services": [],
784
+ "value_proposition": "",
785
+ "target_market": "",
786
+ "competitive_advantages": [],
787
+ "raw_research": ""
788
  }
789
 
790
+ try:
791
+ iteration = 0
792
+ async for event in agent.run(task, max_iterations=12):
793
+ event_type = event.get("type")
794
+ iteration += 1
795
+ progress_pct = min(0.2 + (iteration * 0.06), 0.9)
796
+
797
+ if event_type == "tool_call":
798
+ tool = event.get("tool", "")
799
+ if tool == "search_web":
800
+ output += f"🔍 Searching for {company_name} information...\n"
801
+ elif tool == "search_news":
802
+ output += f"📰 Finding recent news about {company_name}...\n"
803
+ elif tool == "save_company":
804
+ output += f"💾 Saving company profile...\n"
805
+ elif tool == "save_fact":
806
+ output += f"📝 Recording business insight...\n"
807
+ yield output
808
+ progress(progress_pct, desc="Researching...")
809
+
810
+ elif event_type == "tool_result":
811
+ tool = event.get("tool", "")
812
+ if tool in ["search_web", "search_news"]:
813
+ result = event.get("result", {})
814
+ count = result.get("count", 0) if isinstance(result, dict) else 0
815
+ output += f" ✅ Found {count} results\n"
816
+ elif tool == "save_company":
817
+ output += " ✅ Company profile saved\n"
818
+ yield output
819
+
820
+ elif event_type == "agent_complete":
821
+ final_answer = event.get("final_answer", "")
822
+ research_data["raw_research"] = final_answer
823
+ research_data["summary"] = final_answer[:500] + "..." if len(final_answer) > 500 else final_answer
824
+
825
+ # Update knowledge base
826
+ knowledge_base["client"] = {
827
+ "name": company_name,
828
+ "profile": research_data,
829
+ "products_services": research_data.get("products_services", []),
830
+ "value_proposition": research_data.get("value_proposition", ""),
831
+ "target_market": research_data.get("target_market", ""),
832
+ "competitive_advantages": research_data.get("competitive_advantages", []),
833
+ "researched_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
834
+ "raw_research": final_answer
835
+ }
836
 
837
+ output += "\n---\n\n"
838
+ output += f"## ✅ {company_name} Profile Complete!\n\n"
839
+ output += "Your company knowledge base has been created. You can now:\n\n"
840
+ output += "1. **Research Prospects** - Go to the Prospects tab\n"
841
+ output += "2. **Generate Emails** - Personalized based on your company\n"
842
+ output += "3. **Use AI Chat** - Ask questions about prospects\n\n"
843
+ output += "---\n\n"
844
+ output += "### 📋 Research Summary\n\n"
845
+ output += final_answer
846
+
847
+ yield output
848
+ progress(1.0, desc="Complete!")
849
+ return
850
+
851
+ except Exception as e:
852
+ logger.error(f"Client setup error: {e}")
853
+ output += f"\n\n⚠️ Research interrupted: {e}\n"
854
+ yield output
855
+
856
+
857
+ # ============================================================================
858
+ # PROSPECT RESEARCH
859
+ # ============================================================================
860
+ async def research_prospect(prospect_name: str, progress=gr.Progress()):
861
+ """Research a prospect company."""
862
+ global knowledge_base
863
+
864
+ if not knowledge_base["client"]["name"]:
865
+ yield "⚠️ **Setup Required**: Please go to the Setup tab and enter your company name first."
866
+ return
867
+
868
+ if not prospect_name or not prospect_name.strip():
869
+ yield "⚠️ Please enter a prospect company name."
870
+ return
871
+
872
+ prospect_name = prospect_name.strip()
873
+ client_name = knowledge_base["client"]["name"]
874
+
875
+ hf_token = os.getenv('HF_TOKEN') or os.getenv('HF_API_TOKEN')
876
+ if not hf_token:
877
+ yield "❌ AI services not configured."
878
+ return
879
 
 
880
  output = f"""
881
+ ## 🎯 Researching: {prospect_name}
882
 
883
+ **On behalf of:** {client_name}
884
 
885
  ---
886
 
 
888
 
889
  """
890
  yield output
891
+ progress(0.1, desc="Initializing...")
892
 
893
  try:
894
  agent = AutonomousMCPAgentHF(
895
  mcp_registry=mcp_registry,
896
  hf_token=hf_token,
897
+ provider=os.getenv('HF_PROVIDER', 'nscale'),
898
+ model=os.getenv('HF_MODEL', 'Qwen/Qwen3-4B-Instruct-2507')
899
  )
 
900
  output += "✅ AI Agent initialized\n\n"
901
  yield output
902
  progress(0.2, desc="Agent ready...")
 
903
  except Exception as e:
904
+ logger.error(f"Agent init failed: {e}")
905
+ yield f"Could not start AI agent: {e}"
906
  return
907
 
908
+ # Build context from client knowledge base
909
+ client_context = f"""
910
+ You are researching {prospect_name} on behalf of {client_name}.
911
+
912
+ About {client_name} (our company):
913
+ {knowledge_base["client"].get("raw_research", "A B2B company looking for new clients.")}
914
+
915
+ Your task:
916
+ 1. Research {prospect_name} thoroughly - company info, products, recent news
917
+ 2. Identify 3-5 key decision makers (CEO, VP Sales, CTO, etc.)
918
+ 3. Identify pain points that {client_name} could help solve
919
+ 4. Save all prospects, contacts, and facts to the database
920
+ 5. Draft a personalized outreach email from {client_name} to {prospect_name}
921
+
922
+ The email should:
923
+ - Reference specific {prospect_name} challenges
924
+ - Explain how {client_name} can help
925
+ - Be professional and personalized
926
+ """
927
+
928
+ prospect_data = {
929
+ "name": prospect_name,
930
+ "summary": "",
931
+ "industry": "",
932
+ "pain_points": [],
933
+ "contacts": [],
934
+ "research_complete": False,
935
  "email_drafted": False,
936
+ "researched_at": datetime.now().strftime("%Y-%m-%d %H:%M")
937
+ }
938
+
939
+ email_data = {
940
+ "to": "",
941
+ "subject": "",
942
+ "body": "",
943
+ "prospect_company": prospect_name,
944
+ "created_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
945
+ "status": "draft"
946
  }
947
 
948
  try:
949
  iteration = 0
950
+ async for event in agent.run(client_context, max_iterations=15):
951
  event_type = event.get("type")
952
  iteration += 1
 
953
  progress_pct = min(0.2 + (iteration * 0.05), 0.9)
954
 
955
  if event_type == "tool_call":
956
  tool = event.get("tool", "")
957
  tool_input = event.get("input", {})
958
 
959
+ if tool == "search_web":
960
+ output += f"🔍 Searching for {prospect_name} information...\n"
961
+ elif tool == "search_news":
962
+ output += f"📰 Finding recent news...\n"
963
+ elif tool == "save_company":
964
+ output += f"💾 Saving company profile...\n"
965
+ elif tool == "save_prospect":
966
+ output += f"🎯 Creating prospect record...\n"
967
+ elif tool == "save_contact":
968
+ name = tool_input.get("name", "contact") if isinstance(tool_input, dict) else "contact"
969
+ output += f"👤 Saving contact: {name}...\n"
970
+ elif tool == "save_fact":
971
+ output += f"📝 Recording insight...\n"
972
+ elif tool == "send_email":
973
+ output += f"✉️ Drafting outreach email...\n"
974
+ if isinstance(tool_input, dict):
975
+ email_data["to"] = tool_input.get("to", "")
976
+ email_data["subject"] = tool_input.get("subject", f"Introduction from {client_name}")
977
+ email_data["body"] = tool_input.get("body", "")
978
 
979
+ yield output
980
+ progress(progress_pct, desc="Researching...")
 
 
 
 
981
 
982
  elif event_type == "tool_result":
983
  tool = event.get("tool", "")
984
  result = event.get("result", {})
985
 
986
+ if tool in ["search_web", "search_news"]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
987
  count = result.get("count", 0) if isinstance(result, dict) else 0
988
  output += f" ✅ Found {count} results\n"
989
+ elif tool == "save_company":
990
+ output += " ✅ Company saved\n"
991
+ elif tool == "save_prospect":
992
+ output += " ✅ Prospect created\n"
993
+ prospect_data["research_complete"] = True
994
+ elif tool == "save_contact":
995
+ output += " ✅ Contact saved\n"
996
+ # Track contact
997
+ contact_input = event.get("input", {})
998
+ if isinstance(contact_input, dict):
999
+ prospect_data["contacts"].append({
1000
+ "name": contact_input.get("name", "Unknown"),
1001
+ "title": contact_input.get("title", "Unknown")
1002
+ })
1003
+ knowledge_base["contacts"].append({
1004
+ "name": contact_input.get("name", "Unknown"),
1005
+ "title": contact_input.get("title", "Unknown"),
1006
+ "company": prospect_name
1007
+ })
1008
  elif tool == "send_email":
 
1009
  output += " ✅ Email drafted\n"
1010
+ prospect_data["email_drafted"] = True
 
 
 
 
 
 
 
 
1011
 
1012
  yield output
1013
 
 
 
 
1014
  elif event_type == "agent_complete":
1015
  final_answer = event.get("final_answer", "")
1016
+ prospect_data["summary"] = final_answer[:300] + "..." if len(final_answer) > 300 else final_answer
1017
+
1018
+ # Save to knowledge base
1019
+ knowledge_base["prospects"].append(prospect_data)
1020
+
1021
+ if email_data["body"]:
1022
+ knowledge_base["emails"].append(email_data)
1023
 
1024
  output += "\n---\n\n"
1025
+ output += f"## ✅ Research Complete: {prospect_name}\n\n"
1026
+ output += "| Metric | Result |\n"
1027
+ output += "|--------|--------|\n"
1028
+ output += f"| Company Profile | ✅ Saved |\n"
1029
+ output += f"| Decision Makers | {len(prospect_data['contacts'])} found |\n"
1030
+ output += f"| Outreach Email | {'✅ Drafted' if prospect_data['email_drafted'] else '⏳ Pending'} |\n\n"
1031
+
1032
+ if email_data["body"]:
1033
+ output += "---\n\n"
1034
+ output += "### ✉️ Drafted Email\n\n"
1035
+ output += f"**To:** {email_data['to']}\n\n"
1036
+ output += f"**Subject:** {email_data['subject']}\n\n"
1037
+ output += f"```\n{email_data['body']}\n```\n\n"
1038
+
1039
+ output += "---\n\n"
1040
+ output += "### 📋 Research Summary\n\n"
1041
  output += final_answer
1042
 
1043
  yield output
1044
  progress(1.0, desc="Complete!")
1045
+ return
 
 
 
 
 
 
 
 
 
 
1046
 
1047
  except Exception as e:
1048
+ logger.error(f"Prospect research error: {e}")
1049
+ output += f"\n\n⚠️ Research interrupted: {e}\n"
1050
  yield output
1051
 
1052
 
1053
+ # ============================================================================
1054
+ # AI CHAT
1055
+ # ============================================================================
1056
+ def chat_with_ai(message: str, history: list) -> tuple:
1057
+ """AI chat for prospect engagement."""
1058
+ if not knowledge_base["client"]["name"]:
1059
+ return history + [[message, "⚠️ Please complete the Setup first. I need to know about your company before I can help with prospect conversations."]], ""
1060
+
1061
+ if not message.strip():
1062
+ return history, ""
1063
+
1064
+ # Build context from knowledge base
1065
+ client_info = f"Client Company: {knowledge_base['client']['name']}\n"
1066
+ if knowledge_base['client'].get('raw_research'):
1067
+ client_info += f"About: {knowledge_base['client']['raw_research'][:500]}...\n"
1068
+
1069
+ prospects_info = ""
1070
+ if knowledge_base["prospects"]:
1071
+ prospects_info = "Researched Prospects:\n"
1072
+ for p in knowledge_base["prospects"][-5:]:
1073
+ prospects_info += f"- {p['name']}: {p.get('summary', 'No summary')[:100]}...\n"
1074
+
1075
+ # For now, provide a helpful response based on context
1076
+ # In production, this would call the LLM with full context
1077
+
1078
+ response = f"""Based on your knowledge base:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1079
 
1080
+ **Your Company:** {knowledge_base['client']['name']}
1081
+ **Prospects Researched:** {len(knowledge_base['prospects'])}
1082
+ **Emails Drafted:** {len(knowledge_base['emails'])}
1083
 
1084
+ I can help you with:
1085
+ 1. **Talking points** for prospect calls
1086
+ 2. **Email refinement** suggestions
1087
+ 3. **Objection handling** strategies
1088
+ 4. **Prospect insights** from research
1089
+
1090
+ What would you like help with?"""
1091
+
1092
+ # Add to history
1093
+ new_history = history + [[message, response]]
1094
+ knowledge_base["chat_history"].append({"user": message, "assistant": response})
1095
+
1096
+ return new_history, ""
1097
+
1098
+
1099
+ # ============================================================================
1100
+ # HANDOFF PACKET GENERATION
1101
+ # ============================================================================
1102
+ def generate_handoff_packet(prospect_name: str) -> str:
1103
+ """Generate a comprehensive handoff packet for a prospect."""
1104
+ if not prospect_name:
1105
+ return "⚠️ Please select a prospect to generate handoff packet."
1106
+
1107
+ # Find the prospect
1108
+ prospect = None
1109
+ for p in knowledge_base["prospects"]:
1110
+ if p["name"].lower() == prospect_name.lower():
1111
+ prospect = p
1112
+ break
1113
+
1114
+ if not prospect:
1115
+ return f"⚠️ Prospect '{prospect_name}' not found. Please research them first."
1116
+
1117
+ # Find related email
1118
+ email = None
1119
+ for e in knowledge_base["emails"]:
1120
+ if e["prospect_company"].lower() == prospect_name.lower():
1121
+ email = e
1122
+ break
1123
+
1124
+ client_name = knowledge_base["client"]["name"]
1125
+
1126
+ packet = f"""
1127
+ # 📋 Sales Handoff Packet
1128
+
1129
+ ## {prospect["name"]}
1130
+
1131
+ **Prepared by:** CX AI Agent
1132
+ **On behalf of:** {client_name}
1133
+ **Date:** {datetime.now().strftime("%Y-%m-%d")}
1134
+
1135
+ ---
1136
+
1137
+ ## 1. Company Overview
1138
+
1139
+ {prospect.get("summary", "No summary available.")}
1140
+
1141
+ **Industry:** {prospect.get("industry", "Unknown")}
1142
+ **Research Date:** {prospect.get("researched_at", "Unknown")}
1143
+
1144
+ ---
1145
+
1146
+ ## 2. Key Decision Makers
1147
 
 
1148
  """
1149
 
1150
+ if prospect.get("contacts"):
1151
+ for c in prospect["contacts"]:
1152
+ packet += f"- **{c.get('name', 'Unknown')}** - {c.get('title', 'Unknown title')}\n"
1153
+ else:
1154
+ packet += "No contacts identified yet.\n"
1155
 
1156
+ packet += """
1157
+ ---
 
1158
 
1159
+ ## 3. Pain Points & Opportunities
1160
 
1161
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1162
 
1163
+ if prospect.get("pain_points"):
1164
+ for pp in prospect["pain_points"]:
1165
+ packet += f"- {pp}\n"
1166
+ else:
1167
+ packet += "Pain points to be identified during discovery call.\n"
1168
 
1169
+ packet += f"""
1170
+ ---
 
 
 
 
 
 
1171
 
1172
+ ## 4. Recommended Approach
1173
 
1174
+ ### Talking Points
1175
+ 1. Open with reference to their recent news/challenges
1176
+ 2. Connect {client_name}'s solutions to their pain points
1177
+ 3. Share relevant case study or success story
1178
+ 4. Propose specific next step (demo, deeper dive, etc.)
1179
 
1180
+ ### Objection Handling
1181
+ - **"We're happy with current solution"** → Ask about scaling challenges
1182
+ - **"Not the right time"** → Offer to share insights for future reference
1183
+ - **"Too expensive"** → Focus on ROI and long-term value
 
1184
 
1185
+ ---
1186
 
1187
+ ## 5. Drafted Outreach Email
 
 
 
 
 
 
 
 
 
1188
 
1189
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1190
 
1191
+ if email:
1192
+ packet += f"""**To:** {email.get("to", "Not specified")}
1193
+ **Subject:** {email.get("subject", "No subject")}
1194
 
1195
+ ---
 
 
 
 
 
 
 
 
 
1196
 
1197
+ {email.get("body", "No email body.")}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1198
 
1199
+ ---
1200
+ """
1201
+ else:
1202
+ packet += "No email drafted yet. Research the prospect to generate one.\n"
1203
+
1204
+ packet += f"""
1205
+ ---
1206
+
1207
+ ## 6. Next Steps
1208
+
1209
+ - [ ] Review and personalize outreach email
1210
+ - [ ] Schedule initial outreach
1211
+ - [ ] Prepare for discovery call
1212
+ - [ ] Update CRM with research findings
1213
+
1214
+ ---
1215
+
1216
+ *Generated by CX AI Agent on behalf of {client_name}*
1217
+ """
1218
+
1219
+ return packet
1220
+
1221
+
1222
+ def get_prospect_choices():
1223
+ """Get list of prospect names for dropdown."""
1224
+ return [p["name"] for p in knowledge_base["prospects"]] if knowledge_base["prospects"] else []
1225
 
1226
 
1227
  # ============================================================================
1228
+ # GRADIO UI
1229
  # ============================================================================
1230
+ def create_app():
1231
+ """Create the Gradio application."""
1232
 
1233
  with gr.Blocks(
1234
  title="CX AI Agent - B2B Sales Intelligence",
 
1241
  css=ENTERPRISE_CSS
1242
  ) as demo:
1243
 
1244
+ # Header
1245
  gr.HTML("""
1246
  <div class="main-header">
1247
  <div class="header-logo">
 
1259
  </div>
1260
  """)
1261
 
1262
+ # Main tabs
1263
  with gr.Tabs() as tabs:
1264
 
1265
+ # ===== SETUP TAB =====
1266
+ with gr.Tab("⚙️ Setup", id=0):
1267
+ gr.HTML("""
1268
+ <div style="padding: 8px 0 16px 0;">
1269
+ <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Company Setup</h2>
1270
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Enter your company name to build your knowledge base</p>
1271
+ </div>
1272
+ """)
1273
+
1274
+ with gr.Row():
1275
+ with gr.Column(scale=1):
1276
+ gr.HTML("""
1277
+ <div class="action-card">
1278
+ <h3>🏢 Your Company</h3>
1279
+ <p>Enter your company name below. Our AI will research your company and build a knowledge base that will be used to personalize all prospect outreach.</p>
1280
+ </div>
1281
+ """)
1282
+
1283
+ client_name_input = gr.Textbox(
1284
+ label="Company Name",
1285
+ placeholder="Enter your company name (e.g., Acme Corp)",
1286
+ lines=1
1287
+ )
1288
+
1289
+ with gr.Row():
1290
+ setup_btn = gr.Button("🚀 Setup My Company", variant="primary", size="lg")
1291
+ reset_btn = gr.Button("🗑️ Reset All", variant="stop", size="sm")
1292
+
1293
+ gr.HTML("""
1294
+ <div class="action-card" style="margin-top: 16px;">
1295
+ <h3>💡 What happens next?</h3>
1296
+ <ol style="margin: 0; padding-left: 20px;">
1297
+ <li>AI researches your company thoroughly</li>
1298
+ <li>Builds a knowledge base with your products, value prop, etc.</li>
1299
+ <li>Uses this to personalize all prospect communications</li>
1300
+ </ol>
1301
+ </div>
1302
+ """)
1303
+
1304
+ with gr.Column(scale=2):
1305
+ setup_output = gr.Markdown(
1306
+ value="*Enter your company name and click 'Setup My Company' to begin.*"
1307
+ )
1308
+
1309
+ # ===== DASHBOARD TAB =====
1310
+ with gr.Tab("📊 Dashboard", id=1):
1311
+ client_status = gr.HTML(get_client_status_html())
1312
+
1313
  with gr.Row():
1314
  with gr.Column(scale=4):
1315
  gr.HTML("""
1316
  <div style="padding: 8px 0;">
1317
  <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Sales Command Center</h2>
1318
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Overview of your AI-powered sales pipeline</p>
1319
  </div>
1320
  """)
1321
  with gr.Column(scale=1):
1322
+ refresh_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
 
 
1323
 
 
 
 
1324
  with gr.Row():
1325
  prospects_stat = gr.HTML(get_stat_html("0", "Prospects Researched", "var(--primary-blue)"))
1326
  emails_stat = gr.HTML(get_stat_html("0", "Emails Drafted", "var(--success-green)"))
 
1334
 
1335
  gr.HTML("<div style='height: 24px;'></div>")
1336
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1337
  with gr.Row():
1338
+ with gr.Column():
1339
  gr.HTML("""
1340
  <div class="action-card">
1341
+ <h3>🎯 Workflow</h3>
1342
  <ol style="margin: 0; padding-left: 20px;">
1343
+ <li style="margin-bottom: 10px;"><strong>Setup</strong> - Enter your company name</li>
1344
+ <li style="margin-bottom: 10px;"><strong>Research</strong> - Find and analyze prospects</li>
1345
+ <li style="margin-bottom: 10px;"><strong>Engage</strong> - Use AI-drafted emails</li>
1346
+ <li><strong>Handoff</strong> - Generate sales packets</li>
1347
  </ol>
1348
  </div>
1349
  """)
1350
+ with gr.Column():
1351
  gr.HTML("""
1352
  <div class="action-card">
1353
+ <h3>✨ Features</h3>
1354
  <ul style="margin: 0; padding-left: 20px;">
1355
+ <li style="margin-bottom: 8px;">AI-powered company research</li>
1356
+ <li style="margin-bottom: 8px;">Decision maker identification</li>
1357
+ <li style="margin-bottom: 8px;">Personalized email drafting</li>
1358
+ <li style="margin-bottom: 8px;">Sales handoff packet generation</li>
1359
+ <li>AI chat for prospect engagement</li>
1360
  </ul>
1361
  </div>
1362
  """)
1363
 
1364
+ # ===== PROSPECTS TAB =====
1365
+ with gr.Tab("🎯 Prospects", id=2):
1366
+ client_status_2 = gr.HTML(get_client_status_html())
1367
+
1368
  gr.HTML("""
1369
  <div style="padding: 8px 0 16px 0;">
1370
  <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Prospect Research</h2>
1371
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Research target companies and find decision makers</p>
1372
  </div>
1373
  """)
1374
 
1375
  with gr.Row():
 
1376
  with gr.Column(scale=1):
1377
+ gr.HTML("""
1378
+ <div class="action-card">
1379
+ <h3>🔍 Research a Prospect</h3>
1380
+ <p>Enter a company you want to sell to. Our AI will research them and draft personalized outreach.</p>
1381
+ </div>
1382
+ """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1383
 
1384
+ prospect_input = gr.Textbox(
1385
+ label="Prospect Company Name",
1386
+ placeholder="Enter prospect company (e.g., Salesforce)",
1387
+ lines=1
1388
+ )
1389
 
1390
+ research_btn = gr.Button("🚀 Research Prospect", variant="primary", size="lg")
 
 
1391
 
1392
+ gr.HTML("<div style='height: 16px;'></div>")
 
 
 
 
 
 
 
1393
 
1394
+ gr.HTML("<h4 style='margin: 0 0 12px 0; color: var(--text-primary);'>💡 Quick Examples</h4>")
1395
+ with gr.Row():
1396
+ ex1 = gr.Button("Salesforce", size="sm", variant="secondary")
1397
+ ex2 = gr.Button("HubSpot", size="sm", variant="secondary")
1398
+ ex3 = gr.Button("Stripe", size="sm", variant="secondary")
1399
 
1400
+ with gr.Column(scale=2):
1401
  research_output = gr.Markdown(
1402
+ value="*Enter a prospect company name and click 'Research Prospect' to begin.*"
1403
  )
1404
 
1405
+ gr.HTML("<hr style='border: none; border-top: 1px solid var(--border-color); margin: 24px 0;'>")
 
 
 
 
 
 
 
 
 
 
1406
 
 
 
 
 
 
 
 
 
 
 
1407
  with gr.Row():
1408
+ gr.HTML("<h3 style='margin: 0; color: var(--text-primary);'>📋 Researched Prospects</h3>")
1409
+ refresh_prospects_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
 
1410
 
1411
  prospects_list = gr.HTML(get_prospects_html())
1412
 
1413
+ # Example buttons
1414
+ ex1.click(lambda: "Salesforce", outputs=prospect_input)
1415
+ ex2.click(lambda: "HubSpot", outputs=prospect_input)
1416
+ ex3.click(lambda: "Stripe", outputs=prospect_input)
1417
 
1418
+ # ===== EMAILS TAB =====
1419
  with gr.Tab("✉️ Emails", id=3):
1420
+ gr.HTML("""
1421
+ <div style="padding: 8px 0 16px 0;">
1422
+ <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Email Drafts</h2>
1423
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">AI-generated outreach emails personalized for each prospect</p>
1424
+ </div>
1425
+ """)
 
 
 
 
1426
 
1427
+ with gr.Row():
1428
+ refresh_emails_btn = gr.Button("🔄 Refresh", variant="secondary", size="sm")
1429
 
1430
  emails_list = gr.HTML(get_emails_html())
1431
 
1432
+ # ===== HANDOFF TAB =====
1433
+ with gr.Tab("📦 Handoff", id=4):
 
 
 
 
 
1434
  gr.HTML("""
1435
+ <div style="padding: 8px 0 16px 0;">
1436
+ <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">Sales Handoff Packets</h2>
1437
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Generate comprehensive briefing documents for sales handoffs</p>
 
 
1438
  </div>
1439
  """)
1440
 
1441
  with gr.Row():
1442
+ with gr.Column(scale=1):
1443
  gr.HTML("""
1444
  <div class="action-card">
1445
+ <h3>📋 Generate Packet</h3>
1446
+ <p>Select a prospect to generate a complete sales handoff packet including company overview, contacts, talking points, and drafted emails.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1447
  </div>
1448
  """)
1449
 
1450
+ prospect_dropdown = gr.Dropdown(
1451
+ label="Select Prospect",
1452
+ choices=get_prospect_choices(),
1453
+ interactive=True
1454
+ )
 
 
 
 
 
 
1455
 
1456
+ generate_packet_btn = gr.Button("📦 Generate Handoff Packet", variant="primary", size="lg")
1457
+ refresh_dropdown_btn = gr.Button("🔄 Refresh List", variant="secondary", size="sm")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1458
 
1459
+ with gr.Column(scale=2):
1460
+ handoff_output = gr.Markdown(
1461
+ value="*Select a prospect and click 'Generate Handoff Packet' to create a comprehensive sales brief.*"
1462
+ )
 
1463
 
1464
+ # ===== AI CHAT TAB =====
1465
+ with gr.Tab("💬 AI Chat", id=5):
1466
+ gr.HTML("""
1467
+ <div style="padding: 8px 0 16px 0;">
1468
+ <h2 style="margin: 0; font-size: 22px; font-weight: 600; color: var(--text-primary);">AI Sales Assistant</h2>
1469
+ <p style="margin: 4px 0 0 0; font-size: 14px; color: var(--text-secondary);">Chat with AI about your prospects using your knowledge base</p>
1470
+ </div>
1471
+ """)
1472
 
1473
+ chatbot = gr.Chatbot(
1474
+ value=[],
1475
+ height=400,
1476
+ show_label=False
1477
+ )
1478
+
1479
+ with gr.Row():
1480
+ chat_input = gr.Textbox(
1481
+ label="Message",
1482
+ placeholder="Ask about prospects, get talking points, refine emails...",
1483
+ lines=1,
1484
+ scale=4
1485
+ )
1486
+ send_btn = gr.Button("Send", variant="primary", scale=1)
1487
 
1488
+ gr.HTML("""
1489
+ <div class="action-card" style="margin-top: 16px;">
1490
+ <h3>💡 What can I help with?</h3>
1491
+ <ul style="margin: 0; padding-left: 20px;">
1492
+ <li>Talking points for prospect calls</li>
1493
+ <li>Email refinement suggestions</li>
1494
+ <li>Objection handling strategies</li>
1495
+ <li>Prospect insights from research</li>
1496
+ </ul>
1497
+ </div>
1498
+ """)
1499
+
1500
+ # Footer
1501
  gr.HTML("""
1502
  <div class="footer">
1503
  <p style="font-size: 14px; margin-bottom: 4px;"><strong>CX AI Agent</strong> — Enterprise B2B Sales Intelligence</p>
 
1505
  </div>
1506
  """)
1507
 
1508
+ # ===== EVENT HANDLERS =====
1509
+
1510
+ # Setup
1511
+ setup_btn.click(
1512
+ fn=setup_client_company,
1513
+ inputs=[client_name_input],
1514
+ outputs=[setup_output]
1515
+ )
1516
+
1517
+ # Reset
1518
+ reset_btn.click(
1519
+ fn=reset_all_data,
1520
+ outputs=[prospects_stat, emails_stat, contacts_stat, client_status, prospects_list, emails_list, client_name_input, setup_output, research_output]
1521
+ )
1522
+
1523
+ # Dashboard refresh
1524
+ def refresh_dashboard():
1525
+ stats = get_dashboard_stats()
1526
+ return stats[0], stats[1], stats[2], stats[3]
1527
+
1528
+ refresh_btn.click(
1529
+ fn=refresh_dashboard,
1530
+ outputs=[prospects_stat, emails_stat, contacts_stat, client_status]
1531
+ )
1532
+
1533
+ # Prospect research
1534
+ research_btn.click(
1535
+ fn=research_prospect,
1536
+ inputs=[prospect_input],
1537
+ outputs=[research_output]
1538
+ )
1539
+
1540
+ # Refresh prospects list
1541
+ refresh_prospects_btn.click(
1542
+ fn=get_prospects_html,
1543
+ outputs=[prospects_list]
1544
+ )
1545
+
1546
+ # Refresh emails list
1547
+ refresh_emails_btn.click(
1548
+ fn=get_emails_html,
1549
+ outputs=[emails_list]
1550
+ )
1551
+
1552
+ # Handoff packet
1553
+ generate_packet_btn.click(
1554
+ fn=generate_handoff_packet,
1555
+ inputs=[prospect_dropdown],
1556
+ outputs=[handoff_output]
1557
+ )
1558
+
1559
+ # Refresh dropdown
1560
+ refresh_dropdown_btn.click(
1561
+ fn=lambda: gr.Dropdown(choices=get_prospect_choices()),
1562
+ outputs=[prospect_dropdown]
1563
+ )
1564
+
1565
+ # AI Chat
1566
+ send_btn.click(
1567
+ fn=chat_with_ai,
1568
+ inputs=[chat_input, chatbot],
1569
+ outputs=[chatbot, chat_input]
1570
+ )
1571
+
1572
+ chat_input.submit(
1573
+ fn=chat_with_ai,
1574
+ inputs=[chat_input, chatbot],
1575
+ outputs=[chatbot, chat_input]
1576
+ )
1577
+
1578
+ # Update client status on other tabs when setup completes
1579
+ def update_statuses():
1580
+ return get_client_status_html(), get_client_status_html()
1581
+
1582
+ setup_btn.click(
1583
+ fn=update_statuses,
1584
+ outputs=[client_status, client_status_2]
1585
+ )
1586
+
1587
  return demo
1588
 
1589
 
1590
  if __name__ == "__main__":
1591
+ demo = create_app()
1592
  demo.launch(
1593
  server_name="0.0.0.0",
1594
  server_port=7860,