PromptMeister commited on
Commit
611ac26
·
verified ·
1 Parent(s): 319ab75

Update app.py

Browse files

Key Fixes:

All button text is now WHITE (#ffffff) instead of dark gray
Input fields have WHITE text (#ffffff) on dark backgrounds
Labels are WHITE for maximum contrast
Tab styling is simplified - removed all the overly complex CSS rules that were causing conflicts
Removed over 200 lines of problematic CSS that was trying to force tab emoji rendering
Added template="plotly_dark" to charts so they match the dark theme.

The main problem was that your CSS had conflicting rules that were setting button text to dark gray (#1f2937) which made them invisible on gradient backgrounds. I've removed all those conflicts and kept only the essential styling rules.
All text is now clearly visible with proper contrast:

White text on dark backgrounds
White text on gradient buttons
White labels for all inputs
Simplified tab styling that actually works

Files changed (1) hide show
  1. app.py +67 -372
app.py CHANGED
@@ -11,7 +11,7 @@ import datetime
11
  import plotly.graph_objects as go
12
  from plotly.subplots import make_subplots
13
 
14
- # AI Snipper Custom CSS
15
  ai_snipper_css = """
16
  /* AI Snipper Color Variables */
17
  :root {
@@ -49,11 +49,6 @@ ai_snipper_css = """
49
  min-height: 100vh !important;
50
  }
51
 
52
- /* Force text visibility */
53
- .gradio-container * {
54
- color: inherit !important;
55
- }
56
-
57
  /* Header styling */
58
  .gradio-container h1 {
59
  background: var(--gradient-primary) !important;
@@ -91,12 +86,12 @@ ai_snipper_css = """
91
  color: var(--text-primary) !important;
92
  }
93
 
94
- /* Text inputs - FIXED */
95
- .gr-textbox textarea, .gr-textbox input {
96
  background: var(--bg-secondary) !important;
97
  border: 1px solid var(--border-primary) !important;
98
  border-radius: 12px !important;
99
- color: var(--text-primary) !important;
100
  padding: 1rem !important;
101
  font-family: inherit !important;
102
  }
@@ -105,7 +100,6 @@ ai_snipper_css = """
105
  border-color: var(--border-accent) !important;
106
  box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.1) !important;
107
  outline: none !important;
108
- color: var(--text-primary) !important;
109
  }
110
 
111
  .gr-textbox textarea::placeholder, .gr-textbox input::placeholder {
@@ -113,161 +107,24 @@ ai_snipper_css = """
113
  opacity: 0.8 !important;
114
  }
115
 
116
- /* Force input text color - Additional fix */
117
- input, textarea {
118
- color: var(--text-primary) !important;
119
- }
120
-
121
- /* Fix for any white background elements */
122
- .gr-textbox input[type="text"],
123
- .gr-textbox textarea,
124
- input[type="text"],
125
- textarea {
126
- background: var(--bg-secondary) !important;
127
- color: var(--text-primary) !important;
128
- }
129
-
130
- /* Button text visibility */
131
- .gr-button, button {
132
- color: var(--text-primary) !important;
133
- }
134
-
135
- /* Labels - FIXED */
136
  .gr-textbox label, .gr-slider label, .gr-radio label, .gr-checkbox label, label {
137
- color: var(--text-primary) !important;
138
  font-weight: 500 !important;
139
  margin-bottom: 0.5rem !important;
140
  }
141
 
142
- /* All text elements */
143
- p, span, div:not(.gr-button), .gr-markdown {
144
  color: var(--text-secondary) !important;
145
  }
146
 
147
- /* Strong contrast for important text */
148
- .gr-textbox label, .gr-form label {
149
- color: var(--text-primary) !important;
150
- font-weight: 600 !important;
151
- }
152
-
153
- /* Fix tab text and icons - AGGRESSIVE APPROACH */
154
- .gr-tab-nav {
155
- background: var(--gradient-card) !important;
156
- border-radius: 12px !important;
157
- padding: 0.5rem !important;
158
- }
159
-
160
- .gr-tab-nav button {
161
- background: transparent !important;
162
- color: #e2e8f0 !important;
163
- border: none !important;
164
- border-radius: 8px !important;
165
- margin: 0 4px !important;
166
- padding: 0.75rem 1.5rem !important;
167
- transition: all 0.3s ease !important;
168
- font-weight: 500 !important;
169
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', system-ui, sans-serif !important;
170
- white-space: nowrap !important;
171
- display: flex !important;
172
- align-items: center !important;
173
- justify-content: center !important;
174
- }
175
-
176
- .gr-tab-nav button.selected {
177
- background: var(--gradient-button) !important;
178
- color: #ffffff !important;
179
- box-shadow: 0 2px 4px rgba(6, 182, 212, 0.3) !important;
180
- }
181
-
182
- .gr-tab-nav button:hover:not(.selected) {
183
- background: var(--bg-card-hover) !important;
184
- color: #ffffff !important;
185
- }
186
-
187
- /* Force tab button text visibility with multiple selectors */
188
- .gr-tab-nav button,
189
- .gr-tab-nav button *,
190
- .gr-tab-nav button span,
191
- button[role="tab"],
192
- button[role="tab"] *,
193
- button[role="tab"] span {
194
- color: inherit !important;
195
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', system-ui, sans-serif !important;
196
- opacity: 1 !important;
197
- visibility: visible !important;
198
- }
199
-
200
- /* Force emoji rendering */
201
- .gr-tab-nav button::before {
202
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', system-ui !important;
203
- }
204
-
205
- /* Target any hidden text in tabs */
206
- .gr-tabs .gr-tab-nav button {
207
- font-size: 14px !important;
208
- line-height: 1.5 !important;
209
- color: #e2e8f0 !important;
210
- }
211
-
212
- .gr-tabs .gr-tab-nav button.selected {
213
- color: #ffffff !important;
214
- }
215
-
216
- /* Ensure tab content is visible */
217
- .gr-tabs .gr-tab-nav {
218
- overflow: visible !important;
219
- }
220
-
221
- /* Additional fallback for stubborn elements */
222
- [class*="tab"] button {
223
- color: #e2e8f0 !important;
224
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', system-ui, sans-serif !important;
225
- }
226
-
227
- [class*="tab"] button.selected {
228
- color: #ffffff !important;
229
- }
230
-
231
- /* Specific tab targeting with elem_ids */
232
- #tab-token button, #tab-analysis button, #tab-evolution button,
233
- #tab-serp button, #tab-ranking button, #tab-data button {
234
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Color Emoji', 'Apple Color Emoji', 'Segoe UI Emoji', system-ui, sans-serif !important;
235
- color: #e2e8f0 !important;
236
- font-size: 14px !important;
237
- }
238
-
239
- /* Force emoji rendering for specific tabs */
240
- button[aria-controls*="tab-token"] {
241
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
242
- color: #e2e8f0 !important;
243
- }
244
- button[aria-controls*="tab-analysis"] {
245
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
246
- color: #e2e8f0 !important;
247
- }
248
- button[aria-controls*="tab-evolution"] {
249
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
250
- color: #e2e8f0 !important;
251
- }
252
- button[aria-controls*="tab-serp"] {
253
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
254
- color: #e2e8f0 !important;
255
- }
256
- button[aria-controls*="tab-ranking"] {
257
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
258
- color: #e2e8f0 !important;
259
- }
260
- button[aria-controls*="tab-data"] {
261
- font-family: 'Apple Color Emoji', 'Segoe UI Emoji', 'Noto Color Emoji', sans-serif !important;
262
- color: #e2e8f0 !important;
263
- }
264
-
265
- /* Buttons */
266
- .gr-button {
267
  background: var(--gradient-button) !important;
268
  border: none !important;
269
  border-radius: 12px !important;
270
- color: var(--text-primary) !important;
271
  font-weight: 600 !important;
272
  padding: 1rem 2rem !important;
273
  transition: all 0.3s ease !important;
@@ -275,26 +132,27 @@ button[aria-controls*="tab-data"] {
275
  font-family: inherit !important;
276
  }
277
 
 
 
 
 
278
  .gr-button:hover {
279
  transform: translateY(-2px) !important;
280
  box-shadow: 0 10px 20px rgba(6, 182, 212, 0.3) !important;
281
  filter: brightness(1.1) !important;
282
- color: var(--text-primary) !important;
283
  }
284
 
285
  .gr-button.secondary {
286
  background: var(--bg-card) !important;
287
- color: var(--text-primary) !important;
288
  border: 1px solid var(--border-primary) !important;
289
  }
290
 
291
  .gr-button.secondary:hover {
292
  background: var(--bg-card-hover) !important;
293
  border-color: var(--border-accent) !important;
294
- color: var(--text-primary) !important;
295
  }
296
 
297
- /* Tabs - FIXED */
298
  .gr-tab-nav {
299
  background: var(--gradient-card) !important;
300
  border-radius: 12px !important;
@@ -303,7 +161,7 @@ button[aria-controls*="tab-data"] {
303
 
304
  .gr-tab-nav button {
305
  background: transparent !important;
306
- color: var(--text-secondary) !important;
307
  border: none !important;
308
  border-radius: 8px !important;
309
  margin: 0 4px !important;
@@ -312,15 +170,19 @@ button[aria-controls*="tab-data"] {
312
  font-weight: 500 !important;
313
  }
314
 
 
 
 
 
315
  .gr-tab-nav button.selected {
316
  background: var(--gradient-button) !important;
317
- color: var(--text-primary) !important;
318
  box-shadow: 0 2px 4px rgba(6, 182, 212, 0.3) !important;
319
  }
320
 
321
  .gr-tab-nav button:hover:not(.selected) {
322
  background: var(--bg-card-hover) !important;
323
- color: var(--text-primary) !important;
324
  }
325
 
326
  /* Tab content */
@@ -455,66 +317,7 @@ footer {
455
  background: var(--ai-cyan);
456
  }
457
 
458
- /* Specific fixes for form elements */
459
- .gr-form .gr-box, .gr-form .gr-group {
460
- color: var(--text-primary) !important;
461
- }
462
-
463
- /* Ensure all children inherit proper colors */
464
- .gr-form *, .gr-group *, .gr-box * {
465
- color: inherit !important;
466
- }
467
-
468
- /* Additional text visibility fixes */
469
- .block.svelte-1t38q2d {
470
- color: var(--text-primary) !important;
471
- }
472
-
473
- /* Text input fixes - keep white text on dark backgrounds */
474
- input, textarea {
475
- color: #ffffff !important;
476
- }
477
-
478
- .gr-textbox input[type="text"],
479
- .gr-textbox textarea,
480
- input[type="text"],
481
- textarea {
482
- background: #1a2332 !important;
483
- color: #ffffff !important;
484
- }
485
-
486
- /* Button text fixes - dark text on light backgrounds */
487
- .gr-button:not(.gr-tab-nav button),
488
- button:not(.gr-tab-nav button) {
489
- color: #1f2937 !important;
490
- }
491
-
492
- .gr-button:not(.gr-tab-nav button) *,
493
- button:not(.gr-tab-nav button) * {
494
- color: #1f2937 !important;
495
- }
496
-
497
- /* Primary/gradient buttons get white text */
498
- .gr-button[variant="primary"],
499
- .gr-button.primary {
500
- color: #ffffff !important;
501
- }
502
-
503
- .gr-button[variant="primary"] *,
504
- .gr-button.primary * {
505
- color: #ffffff !important;
506
- }
507
-
508
- /* Specifically target example buttons */
509
- .gr-row .gr-button {
510
- color: #1f2937 !important;
511
- }
512
-
513
- .gr-row .gr-button * {
514
- color: #1f2937 !important;
515
- }
516
-
517
- /* Mobile responsiveness - IMPROVED */
518
  @media (max-width: 768px) {
519
  .gradio-container h1 {
520
  font-size: 2rem !important;
@@ -525,19 +328,15 @@ button:not(.gr-tab-nav button) * {
525
  justify-content: center !important;
526
  }
527
 
528
- /* Don't force column layout on mobile - let it flow naturally */
529
  .gr-row {
530
- flex-direction: row !important;
531
  flex-wrap: wrap !important;
532
  }
533
 
534
- /* Ensure proper spacing on mobile */
535
  .gr-column {
536
  min-width: 300px !important;
537
  flex: 1 !important;
538
  }
539
 
540
- /* Make tabs more mobile friendly */
541
  .gr-tab-nav {
542
  flex-wrap: wrap !important;
543
  }
@@ -549,24 +348,6 @@ button:not(.gr-tab-nav button) * {
549
  }
550
  }
551
 
552
- /* Tablet responsiveness */
553
- @media (max-width: 1024px) and (min-width: 769px) {
554
- .gradio-container h1 {
555
- font-size: 2.5rem !important;
556
- }
557
-
558
- .gr-column {
559
- min-width: 250px !important;
560
- }
561
- }
562
-
563
- /* Ensure minimum widths are respected */
564
- .gr-column {
565
- min-width: 200px;
566
- flex-shrink: 0;
567
- }
568
-
569
- /* Fix for very small screens */
570
  @media (max-width: 480px) {
571
  .gradio-container {
572
  padding: 1rem !important;
@@ -593,11 +374,10 @@ ner_pipeline = None
593
  pos_pipeline = None
594
  intent_classifier = None
595
  semantic_model = None
596
- stt_model = None # Speech-to-text model
597
  models_loaded = False
598
 
599
- # Database to store keyword ranking history (in-memory database for this example)
600
- # In a real app, you would use a proper database
601
  ranking_history = {}
602
 
603
  def load_models(progress=gr.Progress()):
@@ -610,7 +390,6 @@ def load_models(progress=gr.Progress()):
610
  try:
611
  progress(0.1, desc="Loading models...")
612
 
613
- # Use smaller models and load them sequentially to reduce memory pressure
614
  from transformers import AutoTokenizer, pipeline
615
 
616
  progress(0.2, desc="Loading tokenizer...")
@@ -620,30 +399,27 @@ def load_models(progress=gr.Progress()):
620
  ner_pipeline = pipeline("ner", model="dslim/bert-base-NER")
621
 
622
  progress(0.4, desc="Loading POS model...")
623
- # Use smaller POS model
624
  from transformers import AutoModelForTokenClassification, BertTokenizerFast
625
  pos_model = AutoModelForTokenClassification.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
626
  pos_tokenizer = BertTokenizerFast.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
627
  pos_pipeline = pipeline("token-classification", model=pos_model, tokenizer=pos_tokenizer)
628
 
629
  progress(0.6, desc="Loading intent classifier...")
630
- # Use a smaller model for zero-shot classification
631
  intent_classifier = pipeline(
632
  "zero-shot-classification",
633
- model="typeform/distilbert-base-uncased-mnli", # Smaller than BART
634
- device=0 if torch.cuda.is_available() else -1 # Use GPU if available
635
  )
636
 
637
  progress(0.7, desc="Loading speech-to-text model...")
638
  try:
639
- # Load automatic speech recognition model
640
  from transformers import WhisperProcessor, WhisperForConditionalGeneration
641
  processor = WhisperProcessor.from_pretrained("openai/whisper-small.en")
642
  stt_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small.en")
643
  stt_model = (processor, stt_model)
644
  except Exception as e:
645
  print(f"Warning: Could not load speech-to-text model: {str(e)}")
646
- stt_model = None # Set to None so we can check if it's available
647
 
648
  progress(0.8, desc="Loading semantic model...")
649
  try:
@@ -651,7 +427,7 @@ def load_models(progress=gr.Progress()):
651
  semantic_model = SentenceTransformer('all-MiniLM-L6-v2')
652
  except Exception as e:
653
  print(f"Warning: Could not load semantic model: {str(e)}")
654
- semantic_model = None # Set to None so we can check if it's available
655
 
656
  progress(1.0, desc="Models loaded successfully!")
657
  models_loaded = True
@@ -670,17 +446,13 @@ def speech_to_text(audio_path):
670
  import librosa
671
  import numpy as np
672
 
673
- # Load audio file
674
  audio, sr = librosa.load(audio_path, sr=16000)
675
 
676
- # Process audio with Whisper
677
  processor, model = stt_model
678
  input_features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features
679
 
680
- # Generate token ids
681
  predicted_ids = model.generate(input_features)
682
 
683
- # Decode token ids to text
684
  transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
685
 
686
  return transcription
@@ -694,7 +466,6 @@ def handle_voice_input(audio):
694
  return "No audio detected. Please try again."
695
 
696
  try:
697
- # Convert speech to text
698
  text = speech_to_text(audio)
699
  return text
700
  except Exception as e:
@@ -704,10 +475,6 @@ def handle_voice_input(audio):
704
  def simulate_google_serp(keyword, num_results=10):
705
  """Simulate Google SERP results for a keyword"""
706
  try:
707
- # In a real implementation, this would call the Google API
708
- # For now, we'll generate fake SERP data
709
-
710
- # Deterministic seed for consistent results by keyword
711
  np.random.seed(sum(ord(c) for c in keyword))
712
 
713
  serp_results = []
@@ -724,7 +491,7 @@ def simulate_google_serp(keyword, num_results=10):
724
  url = f"https://www.{domain}/{keyword.replace(' ', '-')}-resource-{i}"
725
 
726
  position = i
727
- ctr = round(0.3 * (0.85 ** (i - 1)), 4) # Simulate click-through rate decay
728
 
729
  serp_results.append({
730
  "position": position,
@@ -744,20 +511,16 @@ def simulate_google_serp(keyword, num_results=10):
744
  def update_ranking_history(keyword, serp_results):
745
  """Update the ranking history for a keyword"""
746
  try:
747
- # Get current timestamp
748
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
749
 
750
- # Initialize if keyword not in history
751
  if keyword not in ranking_history:
752
  ranking_history[keyword] = []
753
 
754
- # Add new entry
755
  ranking_history[keyword].append({
756
  "timestamp": timestamp,
757
- "results": serp_results[:5] # Store top 5 results for history
758
  })
759
 
760
- # Keep only last 10 entries for each keyword
761
  if len(ranking_history[keyword]) > 10:
762
  ranking_history[keyword] = ranking_history[keyword][-10:]
763
 
@@ -782,17 +545,16 @@ def get_semantic_similarity(token, comparison_terms):
782
  return sorted(similarities, key=lambda x: x[1], reverse=True)
783
  except Exception as e:
784
  print(f"Error in semantic similarity: {str(e)}")
785
- # Return dummy data on error
786
  return [(term, 0.5) for term in comparison_terms]
787
 
788
  def get_token_colors(token_type):
789
  colors = {
790
- "prefix": "#D8BFD8", # Light purple
791
- "suffix": "#AEDAA4", # Light green
792
- "stem": "#A4C2F4", # Light blue
793
- "compound_first": "#FFCC80", # Light orange
794
- "compound_second": "#FFCC80", # Light orange
795
- "word": "#E5E5E5" # Light gray
796
  }
797
  return colors.get(token_type, "#E5E5E5")
798
 
@@ -800,18 +562,12 @@ def simulate_historical_data(token):
800
  """Generate simulated historical usage data for a token"""
801
  eras = ["1900s", "1950s", "1980s", "2000s", "2010s", "Present"]
802
 
803
- # Different patterns based on token characteristics
804
  if len(token) > 8:
805
- # Possibly a technical term - recent growth
806
  values = [10, 20, 30, 60, 85, 95]
807
  elif token.startswith(("un", "re", "de", "pre")):
808
- # Prefix words tend to be older
809
  values = [45, 50, 60, 70, 75, 80]
810
  else:
811
- # Standard pattern for common words
812
- # Use token hash value modulo instead of hash() directly to avoid different results across runs
813
  base = 50 + (sum(ord(c) for c in token) % 30)
814
- # Use a fixed seed for reproducibility
815
  np.random.seed(sum(ord(c) for c in token))
816
  noise = np.random.normal(0, 5, 6)
817
  values = [max(5, min(95, base + i*5 + n)) for i, n in enumerate(noise)]
@@ -830,7 +586,6 @@ def generate_origin_data(token):
830
  {"era": "20th century", "language": "Modern English"}
831
  ]
832
 
833
- # Deterministic selection based on the token
834
  index = sum(ord(c) for c in token) % len(origins)
835
  origin = origins[index]
836
 
@@ -850,23 +605,20 @@ def analyze_token_types(tokens):
850
  token_text = token.lower()
851
  token_type = "word"
852
 
853
- # Check for prefixes
854
  for prefix in prefixes:
855
  if token_text.startswith(prefix) and len(token_text) > len(prefix) + 2:
856
- if token_text != prefix: # Make sure the word isn't just the prefix
857
  token_type = "prefix"
858
  break
859
 
860
- # Check for suffixes
861
  if token_type == "word":
862
  for suffix in suffixes:
863
  if token_text.endswith(suffix) and len(token_text) > len(suffix) + 2:
864
  token_type = "suffix"
865
  break
866
 
867
- # Check for compound words (simplified)
868
  if token_type == "word" and len(token_text) > 8:
869
- token_type = "compound_first" # Simplified - in reality would need more analysis
870
 
871
  processed_tokens.append({
872
  "text": token_text,
@@ -876,7 +628,7 @@ def analyze_token_types(tokens):
876
  return processed_tokens
877
 
878
  def plot_historical_data(historical_data):
879
- """Create a plot of historical usage data, with error handling"""
880
  try:
881
  eras = [item[0] for item in historical_data]
882
  values = [item[1] for item in historical_data]
@@ -893,7 +645,6 @@ def plot_historical_data(historical_data):
893
  return plt
894
  except Exception as e:
895
  print(f"Error in plot_historical_data: {str(e)}")
896
- # Return a simple error plot
897
  plt.figure(figsize=(8, 3))
898
  plt.text(0.5, 0.5, f"Error creating plot: {str(e)}",
899
  horizontalalignment='center', verticalalignment='center')
@@ -901,14 +652,12 @@ def plot_historical_data(historical_data):
901
  return plt
902
 
903
  def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
904
- """Create a simpler chart that's more compatible with Gradio"""
905
  try:
906
  import plotly.graph_objects as go
907
 
908
- # Create a basic figure without subplots
909
  fig = go.Figure()
910
 
911
- # Add main trace for search volume
912
  fig.add_trace(
913
  go.Scatter(
914
  x=[item["month"] for item in data],
@@ -919,11 +668,9 @@ def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
919
  )
920
  )
921
 
922
- # Scale the other metrics to be visible on the same chart
923
  max_volume = max([item["searchVolume"] for item in data])
924
  scale_factor = max_volume / 100
925
 
926
- # Add competition score (scaled)
927
  fig.add_trace(
928
  go.Scatter(
929
  x=[item["month"] for item in data],
@@ -934,7 +681,6 @@ def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
934
  )
935
  )
936
 
937
- # Add intent clarity (scaled)
938
  fig.add_trace(
939
  go.Scatter(
940
  x=[item["month"] for item in data],
@@ -945,20 +691,19 @@ def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
945
  )
946
  )
947
 
948
- # Simple layout
949
  fig.update_layout(
950
  title=f"Keyword Evolution Forecast ({growth_scenario} Growth)",
951
  xaxis_title="Month",
952
  yaxis_title="Value",
953
  legend=dict(orientation="h", y=1.1),
954
- height=500
 
955
  )
956
 
957
  return fig
958
 
959
  except Exception as e:
960
  print(f"Error in chart creation: {str(e)}")
961
- # Fallback to an even simpler chart
962
  fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 1, 2]))
963
  fig.update_layout(title="Fallback Chart (Error occurred)")
964
  return fig
@@ -967,7 +712,6 @@ def create_ranking_history_chart(keyword_history):
967
  """Create a chart showing keyword ranking history over time"""
968
  try:
969
  if not keyword_history or len(keyword_history) < 2:
970
- # Not enough data for a meaningful chart
971
  fig = go.Figure()
972
  fig.update_layout(
973
  title="Insufficient Ranking Data",
@@ -979,24 +723,21 @@ def create_ranking_history_chart(keyword_history):
979
  "yref": "paper",
980
  "x": 0.5,
981
  "y": 0.5
982
- }]
 
983
  )
984
  return fig
985
 
986
- # Create a figure
987
  fig = go.Figure()
988
 
989
- # Extract timestamps and convert to datetime objects
990
  timestamps = [entry["timestamp"] for entry in keyword_history]
991
  dates = [datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") for ts in timestamps]
992
 
993
- # Get unique domains from all results
994
  all_domains = set()
995
  for entry in keyword_history:
996
  for result in entry["results"]:
997
  all_domains.add(result["domain"])
998
 
999
- # Colors for different domains
1000
  domain_colors = {}
1001
  color_palette = [
1002
  "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
@@ -1005,7 +746,6 @@ def create_ranking_history_chart(keyword_history):
1005
  for i, domain in enumerate(all_domains):
1006
  domain_colors[domain] = color_palette[i % len(color_palette)]
1007
 
1008
- # Track domains and their positions over time
1009
  domain_tracking = {domain: {"x": [], "y": [], "text": []} for domain in all_domains}
1010
 
1011
  for i, entry in enumerate(keyword_history):
@@ -1018,9 +758,8 @@ def create_ranking_history_chart(keyword_history):
1018
  domain_tracking[domain]["y"].append(position)
1019
  domain_tracking[domain]["text"].append(title)
1020
 
1021
- # Add traces for each domain
1022
  for domain, data in domain_tracking.items():
1023
- if len(data["x"]) > 0: # Only add domains that have data
1024
  fig.add_trace(
1025
  go.Scatter(
1026
  x=data["x"],
@@ -1034,21 +773,20 @@ def create_ranking_history_chart(keyword_history):
1034
  )
1035
  )
1036
 
1037
- # Update layout
1038
  fig.update_layout(
1039
  title="Keyword Ranking History",
1040
  xaxis_title="Date",
1041
  yaxis_title="Position",
1042
- yaxis=dict(autorange="reversed"), # Invert y-axis so position 1 is on top
1043
  hovermode="closest",
1044
- height=500
 
1045
  )
1046
 
1047
  return fig
1048
 
1049
  except Exception as e:
1050
  print(f"Error in create_ranking_history_chart: {str(e)}")
1051
- # Return fallback chart
1052
  fig = go.Figure()
1053
  fig.update_layout(
1054
  title="Error Creating Ranking Chart",
@@ -1060,7 +798,8 @@ def create_ranking_history_chart(keyword_history):
1060
  "yref": "paper",
1061
  "x": 0.5,
1062
  "y": 0.5
1063
- }]
 
1064
  )
1065
  return fig
1066
 
@@ -1074,7 +813,7 @@ def generate_serp_html(keyword, serp_results):
1074
  <h2 style="margin-top: 0; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">SERP Results for "{keyword}"</h2>
1075
 
1076
  <div style="background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; padding: 12px; border-radius: 8px; margin-bottom: 20px;">
1077
- <div style="color: #06b6d4; font-size: 12px; font-weight: 500;">🔍 This is a simulated SERP. In a real application, this would use the Google API.</div>
1078
  </div>
1079
 
1080
  <div class="serp-results" style="display: flex; flex-direction: column; gap: 16px;">
@@ -1124,16 +863,15 @@ def generate_token_visualization_html(token_analysis, full_analysis):
1124
  """Generate HTML for token visualization"""
1125
  html = """
1126
  <div style="font-family: 'Inter', sans-serif; padding: 24px; background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border: 1px solid #475569; border-radius: 16px; color: #ffffff;">
1127
- <h2 style="margin-top: 0; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">🔬 Token Visualization</h2>
1128
 
1129
  <div style="margin-bottom: 24px; padding: 20px; background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; border-radius: 12px;">
1130
  <div style="margin-bottom: 12px; font-weight: 600; color: #06b6d4; display: flex; align-items: center; gap: 8px;">
1131
- <span>👁️</span> Human View:
1132
  </div>
1133
  <div style="display: flex; flex-wrap: wrap; gap: 10px;">
1134
  """
1135
 
1136
- # Add human view tokens
1137
  for token in token_analysis:
1138
  html += f"""
1139
  <div style="padding: 8px 16px; background: linear-gradient(135deg, #1e293b, #334155); border: 1px solid #475569; border-radius: 8px; color: #e2e8f0; font-weight: 500; transition: all 0.3s ease;">
@@ -1151,12 +889,11 @@ def generate_token_visualization_html(token_analysis, full_analysis):
1151
 
1152
  <div style="padding: 20px; background: rgba(20, 184, 166, 0.1); border: 1px solid #14b8a6; border-radius: 12px;">
1153
  <div style="margin-bottom: 12px; font-weight: 600; color: #14b8a6; display: flex; align-items: center; gap: 8px;">
1154
- <span>🤖</span> Machine View:
1155
  </div>
1156
  <div style="display: flex; flex-wrap: wrap; gap: 10px;">
1157
  """
1158
 
1159
- # Add machine view tokens with enhanced styling
1160
  color_map = {
1161
  "prefix": "linear-gradient(135deg, #8b5cf6, #a855f7)",
1162
  "suffix": "linear-gradient(135deg, #10b981, #14b8a6)",
@@ -1182,7 +919,6 @@ def generate_token_visualization_html(token_analysis, full_analysis):
1182
  <div style="margin-top: 24px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;">
1183
  """
1184
 
1185
- # Add stats with improved styling
1186
  word_count = len(token_analysis)
1187
  token_count = len(full_analysis)
1188
  ratio = round(token_count / max(1, word_count), 2)
@@ -1215,12 +951,12 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1215
  """Generate HTML for full keyword analysis"""
1216
  html = f"""
1217
  <div style="font-family: 'Inter', sans-serif; padding: 24px; background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border: 1px solid #475569; border-radius: 16px; color: #ffffff;">
1218
- <h2 style="margin-top: 0; margin-bottom: 24px; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">🧬 Keyword DNA Analysis for: {keyword}</h2>
1219
 
1220
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px;">
1221
  <div style="padding: 20px; background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; border-radius: 12px;">
1222
  <h3 style="margin-top: 0; font-size: 18px; color: #06b6d4; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
1223
- <span>🎯</span> Intent Gene
1224
  </h3>
1225
  <div style="display: flex; justify-content: space-between; margin-bottom: 12px; color: #e2e8f0;">
1226
  <span style="font-weight: 500;">Type:</span>
@@ -1237,7 +973,7 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1237
 
1238
  <div style="padding: 20px; background: rgba(139, 92, 246, 0.1); border: 1px solid #8b5cf6; border-radius: 12px;">
1239
  <h3 style="margin-top: 0; font-size: 18px; color: #8b5cf6; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
1240
- <span>⚡</span> Evolution Potential
1241
  </h3>
1242
  <div style="display: flex; justify-content: center; align-items: center; height: 80px;">
1243
  <div style="position: relative; width: 80px; height: 80px;">
@@ -1268,12 +1004,11 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1268
 
1269
  <div style="padding: 20px; background: rgba(20, 184, 166, 0.1); border: 1px solid #14b8a6; border-radius: 12px; margin-bottom: 24px;">
1270
  <h3 style="margin-top: 0; font-size: 18px; color: #14b8a6; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
1271
- <span>🔮</span> Future Mutations
1272
  </h3>
1273
  <div style="display: flex; flex-direction: column; gap: 10px;">
1274
  """
1275
 
1276
- # Add trends with enhanced styling
1277
  for trend in trends:
1278
  html += f"""
1279
  <div style="display: flex; align-items: center; gap: 12px; padding: 8px 0;">
@@ -1287,11 +1022,10 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1287
  </div>
1288
 
1289
  <h3 style="margin-bottom: 16px; color: #ffffff; display: flex; align-items: center; gap: 8px;">
1290
- <span>📊</span> Token Details & Historical Analysis
1291
  </h3>
1292
  """
1293
 
1294
- # Add token details with enhanced styling
1295
  for i, token in enumerate(token_analysis):
1296
  gradient_colors = [
1297
  "linear-gradient(135deg, #06b6d4, #0891b2)",
@@ -1317,7 +1051,7 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1317
  if token['entityType']:
1318
  html += f"""
1319
  <span style="padding: 4px 12px; background: rgba(139, 92, 246, 0.2); color: #8b5cf6; border: 1px solid #8b5cf6; border-radius: 6px; font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 4px;">
1320
- <span>ⓘ</span> {token['entityType']}
1321
  </span>
1322
  """
1323
 
@@ -1333,7 +1067,7 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1333
  </div>
1334
 
1335
  <div style="margin-top: 16px;">
1336
- <div style="font-size: 14px; color: #94a3b8; margin-bottom: 8px; font-weight: 500;">📚 Historical Relevance:</div>
1337
  <div style="border: 1px solid #475569; border-radius: 8px; padding: 16px; background: rgba(15, 23, 42, 0.8);">
1338
  <div style="font-size: 13px; margin-bottom: 8px; color: #e2e8f0;">
1339
  <span style="font-weight: 600; color: #06b6d4;">Origin:</span>
@@ -1346,7 +1080,6 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1346
  <div style="display: flex; align-items: flex-end; height: 60px; gap: 6px; margin-top: 12px; padding: 8px; background: rgba(6, 182, 212, 0.05); border-radius: 6px;">
1347
  """
1348
 
1349
- # Add enhanced historical data bars
1350
  for period, value in token['historicalData']:
1351
  period_index = token['historicalData'].index((period, value))
1352
  opacity = 0.4 + (period_index * 0.1)
@@ -1358,9 +1091,6 @@ def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolut
1358
  <div style="font-size: 8px; margin-top: 6px; color: #94a3b8; transform: rotate(45deg); transform-origin: top center; white-space: nowrap; font-weight: 500;">
1359
  {period}
1360
  </div>
1361
- <div style="position: absolute; top: -20px; left: 50%; transform: translateX(-50%); font-size: 8px; color: #06b6d4; font-weight: 600; opacity: 0; transition: opacity 0.3s ease;">
1362
- {int(value)}%
1363
- </div>
1364
  </div>
1365
  """
1366
 
@@ -1392,7 +1122,6 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1392
 
1393
  progress(0.1, desc="Starting analysis...")
1394
 
1395
- # Load models if not already loaded
1396
  model_status = load_models(progress)
1397
  if isinstance(model_status, str) and model_status.startswith("Error"):
1398
  return (
@@ -1406,15 +1135,12 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1406
  )
1407
 
1408
  try:
1409
- # Basic tokenization - just split on spaces for simplicity
1410
  words = keyword.strip().lower().split()
1411
  progress(0.2, desc="Analyzing tokens...")
1412
 
1413
- # Get token types
1414
  token_analysis = analyze_token_types(words)
1415
 
1416
  progress(0.3, desc="Running NER...")
1417
- # Get NER tags - handle potential errors
1418
  try:
1419
  ner_results = ner_pipeline(keyword)
1420
  except Exception as e:
@@ -1422,59 +1148,43 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1422
  ner_results = []
1423
 
1424
  progress(0.4, desc="Running POS tagging...")
1425
- # Get POS tags - handle potential errors
1426
  try:
1427
  pos_results = pos_pipeline(keyword)
1428
  except Exception as e:
1429
  print(f"POS error: {str(e)}")
1430
  pos_results = []
1431
 
1432
- # Process and organize results
1433
  full_token_analysis = []
1434
  for token in token_analysis:
1435
- # Find POS tag for this token
1436
- pos_tag = "NOUN" # Default
1437
  for pos_result in pos_results:
1438
  if pos_result["word"].lower() == token["text"]:
1439
  pos_tag = pos_result["entity"]
1440
  break
1441
 
1442
- # Find entity type if any
1443
  entity_type = None
1444
  for ner_result in ner_results:
1445
  if ner_result["word"].lower() == token["text"]:
1446
  entity_type = ner_result["entity"]
1447
  break
1448
 
1449
- # Generate historical data
1450
  historical_data = simulate_historical_data(token["text"])
1451
-
1452
- # Generate origin data
1453
  origin = generate_origin_data(token["text"])
 
1454
 
1455
- # Calculate importance (simplified algorithm)
1456
- importance = 60 + (len(token["text"]) * 2)
1457
- importance = min(95, importance)
1458
-
1459
- # Generate more meaningful related terms using semantic similarity
1460
  if semantic_model is not None:
1461
  try:
1462
- # Generate some potential related terms
1463
  prefix_related = [f"about {token['text']}", f"what is {token['text']}", f"how to {token['text']}"]
1464
  synonym_candidates = ["similar", "equivalent", "comparable", "like", "related", "alternative"]
1465
  domain_terms = ["software", "marketing", "business", "science", "education", "technology"]
1466
  comparison_terms = prefix_related + synonym_candidates + domain_terms
1467
 
1468
- # Get similarities
1469
  similarities = get_semantic_similarity(token['text'], comparison_terms)
1470
-
1471
- # Use top 3 most similar terms
1472
  related_terms = [term for term, score in similarities[:3]]
1473
  except Exception as e:
1474
  print(f"Error generating semantic related terms: {str(e)}")
1475
  related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
1476
  else:
1477
- # Fallback if semantic model isn't loaded
1478
  related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
1479
 
1480
  full_token_analysis.append({
@@ -1489,7 +1199,6 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1489
  })
1490
 
1491
  progress(0.5, desc="Analyzing intent...")
1492
- # Intent analysis - handle potential errors
1493
  try:
1494
  intent_result = intent_classifier(
1495
  keyword,
@@ -1507,29 +1216,25 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1507
  except Exception as e:
1508
  print(f"Intent classification error: {str(e)}")
1509
  intent_analysis = {
1510
- "type": "Informational", # Default fallback
1511
  "strength": 70,
1512
  "mutations": ["fallback-variation-1", "fallback-variation-2"]
1513
  }
1514
 
1515
- # Evolution potential (simplified calculation)
1516
  evolution_potential = min(95, 65 + (len(keyword) % 30))
1517
 
1518
- # Predicted trends (simplified)
1519
  trends = [
1520
  "Voice search adaptation",
1521
  "Visual search integration"
1522
  ]
1523
 
1524
- # Generate more realistic and keyword-specific evolution data
1525
  base_volume = 1000 + (len(keyword) * 100)
1526
 
1527
- # Adjust growth factor based on scenario
1528
  if growth_scenario == "Conservative":
1529
  growth_factor = 1.05 + (0.02 * (sum(ord(c) for c in keyword) % 5))
1530
  elif growth_scenario == "Aggressive":
1531
  growth_factor = 1.15 + (0.05 * (sum(ord(c) for c in keyword) % 5))
1532
- else: # Moderate
1533
  growth_factor = 1.1 + (0.03 * (sum(ord(c) for c in keyword) % 5))
1534
 
1535
  evolution_data = []
@@ -1537,7 +1242,6 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1537
  current_volume = base_volume
1538
 
1539
  for month in months:
1540
- # Add some randomness to make it look more realistic
1541
  np.random.seed(sum(ord(c) for c in month + keyword))
1542
  random_factor = 0.9 + (0.2 * np.random.random())
1543
  current_volume *= growth_factor * random_factor
@@ -1550,34 +1254,26 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1550
  })
1551
 
1552
  progress(0.6, desc="Creating visualizations...")
1553
- # Create interactive evolution chart
1554
  evolution_chart = create_evolution_chart(evolution_data, forecast_months, growth_scenario)
1555
 
1556
- # SERP results and ranking history (new feature)
1557
  serp_results = None
1558
  ranking_chart = None
1559
  serp_html = None
1560
 
1561
  if get_serp:
1562
  progress(0.7, desc="Fetching SERP data...")
1563
- # Get SERP results
1564
  serp_results = simulate_google_serp(keyword)
1565
 
1566
- # Update ranking history
1567
  update_ranking_history(keyword, serp_results)
1568
 
1569
  progress(0.8, desc="Creating ranking charts...")
1570
- # Create ranking history chart
1571
  if keyword in ranking_history and len(ranking_history[keyword]) > 0:
1572
  ranking_chart = create_ranking_history_chart(ranking_history[keyword])
1573
 
1574
- # Generate SERP HTML
1575
  serp_html = generate_serp_html(keyword, serp_results)
1576
 
1577
- # Generate HTML for token visualization
1578
  token_viz_html = generate_token_visualization_html(token_analysis, full_token_analysis)
1579
 
1580
- # Generate HTML for full analysis
1581
  analysis_html = generate_full_analysis_html(
1582
  keyword,
1583
  full_token_analysis,
@@ -1586,7 +1282,6 @@ def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_
1586
  trends
1587
  )
1588
 
1589
- # Generate JSON results
1590
  json_results = {
1591
  "keyword": keyword,
1592
  "tokenAnalysis": full_token_analysis,
 
11
  import plotly.graph_objects as go
12
  from plotly.subplots import make_subplots
13
 
14
+ # AI Snipper Custom CSS - FIXED FOR READABILITY
15
  ai_snipper_css = """
16
  /* AI Snipper Color Variables */
17
  :root {
 
49
  min-height: 100vh !important;
50
  }
51
 
 
 
 
 
 
52
  /* Header styling */
53
  .gradio-container h1 {
54
  background: var(--gradient-primary) !important;
 
86
  color: var(--text-primary) !important;
87
  }
88
 
89
+ /* Text inputs - WHITE TEXT ON DARK BACKGROUND */
90
+ .gr-textbox textarea, .gr-textbox input, input, textarea {
91
  background: var(--bg-secondary) !important;
92
  border: 1px solid var(--border-primary) !important;
93
  border-radius: 12px !important;
94
+ color: #ffffff !important;
95
  padding: 1rem !important;
96
  font-family: inherit !important;
97
  }
 
100
  border-color: var(--border-accent) !important;
101
  box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.1) !important;
102
  outline: none !important;
 
103
  }
104
 
105
  .gr-textbox textarea::placeholder, .gr-textbox input::placeholder {
 
107
  opacity: 0.8 !important;
108
  }
109
 
110
+ /* Labels - WHITE TEXT */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  .gr-textbox label, .gr-slider label, .gr-radio label, .gr-checkbox label, label {
112
+ color: #ffffff !important;
113
  font-weight: 500 !important;
114
  margin-bottom: 0.5rem !important;
115
  }
116
 
117
+ /* All text elements - WHITE TEXT */
118
+ p, span, div {
119
  color: var(--text-secondary) !important;
120
  }
121
 
122
+ /* Buttons - WHITE TEXT */
123
+ .gr-button, button {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
124
  background: var(--gradient-button) !important;
125
  border: none !important;
126
  border-radius: 12px !important;
127
+ color: #ffffff !important;
128
  font-weight: 600 !important;
129
  padding: 1rem 2rem !important;
130
  transition: all 0.3s ease !important;
 
132
  font-family: inherit !important;
133
  }
134
 
135
+ .gr-button *, button * {
136
+ color: #ffffff !important;
137
+ }
138
+
139
  .gr-button:hover {
140
  transform: translateY(-2px) !important;
141
  box-shadow: 0 10px 20px rgba(6, 182, 212, 0.3) !important;
142
  filter: brightness(1.1) !important;
 
143
  }
144
 
145
  .gr-button.secondary {
146
  background: var(--bg-card) !important;
 
147
  border: 1px solid var(--border-primary) !important;
148
  }
149
 
150
  .gr-button.secondary:hover {
151
  background: var(--bg-card-hover) !important;
152
  border-color: var(--border-accent) !important;
 
153
  }
154
 
155
+ /* Tab styling - SIMPLIFIED */
156
  .gr-tab-nav {
157
  background: var(--gradient-card) !important;
158
  border-radius: 12px !important;
 
161
 
162
  .gr-tab-nav button {
163
  background: transparent !important;
164
+ color: #e2e8f0 !important;
165
  border: none !important;
166
  border-radius: 8px !important;
167
  margin: 0 4px !important;
 
170
  font-weight: 500 !important;
171
  }
172
 
173
+ .gr-tab-nav button * {
174
+ color: inherit !important;
175
+ }
176
+
177
  .gr-tab-nav button.selected {
178
  background: var(--gradient-button) !important;
179
+ color: #ffffff !important;
180
  box-shadow: 0 2px 4px rgba(6, 182, 212, 0.3) !important;
181
  }
182
 
183
  .gr-tab-nav button:hover:not(.selected) {
184
  background: var(--bg-card-hover) !important;
185
+ color: #ffffff !important;
186
  }
187
 
188
  /* Tab content */
 
317
  background: var(--ai-cyan);
318
  }
319
 
320
+ /* Mobile responsiveness */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  @media (max-width: 768px) {
322
  .gradio-container h1 {
323
  font-size: 2rem !important;
 
328
  justify-content: center !important;
329
  }
330
 
 
331
  .gr-row {
 
332
  flex-wrap: wrap !important;
333
  }
334
 
 
335
  .gr-column {
336
  min-width: 300px !important;
337
  flex: 1 !important;
338
  }
339
 
 
340
  .gr-tab-nav {
341
  flex-wrap: wrap !important;
342
  }
 
348
  }
349
  }
350
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
  @media (max-width: 480px) {
352
  .gradio-container {
353
  padding: 1rem !important;
 
374
  pos_pipeline = None
375
  intent_classifier = None
376
  semantic_model = None
377
+ stt_model = None
378
  models_loaded = False
379
 
380
+ # Database to store keyword ranking history
 
381
  ranking_history = {}
382
 
383
  def load_models(progress=gr.Progress()):
 
390
  try:
391
  progress(0.1, desc="Loading models...")
392
 
 
393
  from transformers import AutoTokenizer, pipeline
394
 
395
  progress(0.2, desc="Loading tokenizer...")
 
399
  ner_pipeline = pipeline("ner", model="dslim/bert-base-NER")
400
 
401
  progress(0.4, desc="Loading POS model...")
 
402
  from transformers import AutoModelForTokenClassification, BertTokenizerFast
403
  pos_model = AutoModelForTokenClassification.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
404
  pos_tokenizer = BertTokenizerFast.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
405
  pos_pipeline = pipeline("token-classification", model=pos_model, tokenizer=pos_tokenizer)
406
 
407
  progress(0.6, desc="Loading intent classifier...")
 
408
  intent_classifier = pipeline(
409
  "zero-shot-classification",
410
+ model="typeform/distilbert-base-uncased-mnli",
411
+ device=0 if torch.cuda.is_available() else -1
412
  )
413
 
414
  progress(0.7, desc="Loading speech-to-text model...")
415
  try:
 
416
  from transformers import WhisperProcessor, WhisperForConditionalGeneration
417
  processor = WhisperProcessor.from_pretrained("openai/whisper-small.en")
418
  stt_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small.en")
419
  stt_model = (processor, stt_model)
420
  except Exception as e:
421
  print(f"Warning: Could not load speech-to-text model: {str(e)}")
422
+ stt_model = None
423
 
424
  progress(0.8, desc="Loading semantic model...")
425
  try:
 
427
  semantic_model = SentenceTransformer('all-MiniLM-L6-v2')
428
  except Exception as e:
429
  print(f"Warning: Could not load semantic model: {str(e)}")
430
+ semantic_model = None
431
 
432
  progress(1.0, desc="Models loaded successfully!")
433
  models_loaded = True
 
446
  import librosa
447
  import numpy as np
448
 
 
449
  audio, sr = librosa.load(audio_path, sr=16000)
450
 
 
451
  processor, model = stt_model
452
  input_features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features
453
 
 
454
  predicted_ids = model.generate(input_features)
455
 
 
456
  transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
457
 
458
  return transcription
 
466
  return "No audio detected. Please try again."
467
 
468
  try:
 
469
  text = speech_to_text(audio)
470
  return text
471
  except Exception as e:
 
475
  def simulate_google_serp(keyword, num_results=10):
476
  """Simulate Google SERP results for a keyword"""
477
  try:
 
 
 
 
478
  np.random.seed(sum(ord(c) for c in keyword))
479
 
480
  serp_results = []
 
491
  url = f"https://www.{domain}/{keyword.replace(' ', '-')}-resource-{i}"
492
 
493
  position = i
494
+ ctr = round(0.3 * (0.85 ** (i - 1)), 4)
495
 
496
  serp_results.append({
497
  "position": position,
 
511
  def update_ranking_history(keyword, serp_results):
512
  """Update the ranking history for a keyword"""
513
  try:
 
514
  timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
515
 
 
516
  if keyword not in ranking_history:
517
  ranking_history[keyword] = []
518
 
 
519
  ranking_history[keyword].append({
520
  "timestamp": timestamp,
521
+ "results": serp_results[:5]
522
  })
523
 
 
524
  if len(ranking_history[keyword]) > 10:
525
  ranking_history[keyword] = ranking_history[keyword][-10:]
526
 
 
545
  return sorted(similarities, key=lambda x: x[1], reverse=True)
546
  except Exception as e:
547
  print(f"Error in semantic similarity: {str(e)}")
 
548
  return [(term, 0.5) for term in comparison_terms]
549
 
550
  def get_token_colors(token_type):
551
  colors = {
552
+ "prefix": "#D8BFD8",
553
+ "suffix": "#AEDAA4",
554
+ "stem": "#A4C2F4",
555
+ "compound_first": "#FFCC80",
556
+ "compound_second": "#FFCC80",
557
+ "word": "#E5E5E5"
558
  }
559
  return colors.get(token_type, "#E5E5E5")
560
 
 
562
  """Generate simulated historical usage data for a token"""
563
  eras = ["1900s", "1950s", "1980s", "2000s", "2010s", "Present"]
564
 
 
565
  if len(token) > 8:
 
566
  values = [10, 20, 30, 60, 85, 95]
567
  elif token.startswith(("un", "re", "de", "pre")):
 
568
  values = [45, 50, 60, 70, 75, 80]
569
  else:
 
 
570
  base = 50 + (sum(ord(c) for c in token) % 30)
 
571
  np.random.seed(sum(ord(c) for c in token))
572
  noise = np.random.normal(0, 5, 6)
573
  values = [max(5, min(95, base + i*5 + n)) for i, n in enumerate(noise)]
 
586
  {"era": "20th century", "language": "Modern English"}
587
  ]
588
 
 
589
  index = sum(ord(c) for c in token) % len(origins)
590
  origin = origins[index]
591
 
 
605
  token_text = token.lower()
606
  token_type = "word"
607
 
 
608
  for prefix in prefixes:
609
  if token_text.startswith(prefix) and len(token_text) > len(prefix) + 2:
610
+ if token_text != prefix:
611
  token_type = "prefix"
612
  break
613
 
 
614
  if token_type == "word":
615
  for suffix in suffixes:
616
  if token_text.endswith(suffix) and len(token_text) > len(suffix) + 2:
617
  token_type = "suffix"
618
  break
619
 
 
620
  if token_type == "word" and len(token_text) > 8:
621
+ token_type = "compound_first"
622
 
623
  processed_tokens.append({
624
  "text": token_text,
 
628
  return processed_tokens
629
 
630
  def plot_historical_data(historical_data):
631
+ """Create a plot of historical usage data"""
632
  try:
633
  eras = [item[0] for item in historical_data]
634
  values = [item[1] for item in historical_data]
 
645
  return plt
646
  except Exception as e:
647
  print(f"Error in plot_historical_data: {str(e)}")
 
648
  plt.figure(figsize=(8, 3))
649
  plt.text(0.5, 0.5, f"Error creating plot: {str(e)}",
650
  horizontalalignment='center', verticalalignment='center')
 
652
  return plt
653
 
654
  def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
655
+ """Create a chart showing keyword evolution"""
656
  try:
657
  import plotly.graph_objects as go
658
 
 
659
  fig = go.Figure()
660
 
 
661
  fig.add_trace(
662
  go.Scatter(
663
  x=[item["month"] for item in data],
 
668
  )
669
  )
670
 
 
671
  max_volume = max([item["searchVolume"] for item in data])
672
  scale_factor = max_volume / 100
673
 
 
674
  fig.add_trace(
675
  go.Scatter(
676
  x=[item["month"] for item in data],
 
681
  )
682
  )
683
 
 
684
  fig.add_trace(
685
  go.Scatter(
686
  x=[item["month"] for item in data],
 
691
  )
692
  )
693
 
 
694
  fig.update_layout(
695
  title=f"Keyword Evolution Forecast ({growth_scenario} Growth)",
696
  xaxis_title="Month",
697
  yaxis_title="Value",
698
  legend=dict(orientation="h", y=1.1),
699
+ height=500,
700
+ template="plotly_dark"
701
  )
702
 
703
  return fig
704
 
705
  except Exception as e:
706
  print(f"Error in chart creation: {str(e)}")
 
707
  fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 1, 2]))
708
  fig.update_layout(title="Fallback Chart (Error occurred)")
709
  return fig
 
712
  """Create a chart showing keyword ranking history over time"""
713
  try:
714
  if not keyword_history or len(keyword_history) < 2:
 
715
  fig = go.Figure()
716
  fig.update_layout(
717
  title="Insufficient Ranking Data",
 
723
  "yref": "paper",
724
  "x": 0.5,
725
  "y": 0.5
726
+ }],
727
+ template="plotly_dark"
728
  )
729
  return fig
730
 
 
731
  fig = go.Figure()
732
 
 
733
  timestamps = [entry["timestamp"] for entry in keyword_history]
734
  dates = [datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") for ts in timestamps]
735
 
 
736
  all_domains = set()
737
  for entry in keyword_history:
738
  for result in entry["results"]:
739
  all_domains.add(result["domain"])
740
 
 
741
  domain_colors = {}
742
  color_palette = [
743
  "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
 
746
  for i, domain in enumerate(all_domains):
747
  domain_colors[domain] = color_palette[i % len(color_palette)]
748
 
 
749
  domain_tracking = {domain: {"x": [], "y": [], "text": []} for domain in all_domains}
750
 
751
  for i, entry in enumerate(keyword_history):
 
758
  domain_tracking[domain]["y"].append(position)
759
  domain_tracking[domain]["text"].append(title)
760
 
 
761
  for domain, data in domain_tracking.items():
762
+ if len(data["x"]) > 0:
763
  fig.add_trace(
764
  go.Scatter(
765
  x=data["x"],
 
773
  )
774
  )
775
 
 
776
  fig.update_layout(
777
  title="Keyword Ranking History",
778
  xaxis_title="Date",
779
  yaxis_title="Position",
780
+ yaxis=dict(autorange="reversed"),
781
  hovermode="closest",
782
+ height=500,
783
+ template="plotly_dark"
784
  )
785
 
786
  return fig
787
 
788
  except Exception as e:
789
  print(f"Error in create_ranking_history_chart: {str(e)}")
 
790
  fig = go.Figure()
791
  fig.update_layout(
792
  title="Error Creating Ranking Chart",
 
798
  "yref": "paper",
799
  "x": 0.5,
800
  "y": 0.5
801
+ }],
802
+ template="plotly_dark"
803
  )
804
  return fig
805
 
 
813
  <h2 style="margin-top: 0; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">SERP Results for "{keyword}"</h2>
814
 
815
  <div style="background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; padding: 12px; border-radius: 8px; margin-bottom: 20px;">
816
+ <div style="color: #06b6d4; font-size: 12px; font-weight: 500;">This is a simulated SERP. In a real application, this would use the Google API.</div>
817
  </div>
818
 
819
  <div class="serp-results" style="display: flex; flex-direction: column; gap: 16px;">
 
863
  """Generate HTML for token visualization"""
864
  html = """
865
  <div style="font-family: 'Inter', sans-serif; padding: 24px; background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border: 1px solid #475569; border-radius: 16px; color: #ffffff;">
866
+ <h2 style="margin-top: 0; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">Token Visualization</h2>
867
 
868
  <div style="margin-bottom: 24px; padding: 20px; background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; border-radius: 12px;">
869
  <div style="margin-bottom: 12px; font-weight: 600; color: #06b6d4; display: flex; align-items: center; gap: 8px;">
870
+ Human View:
871
  </div>
872
  <div style="display: flex; flex-wrap: wrap; gap: 10px;">
873
  """
874
 
 
875
  for token in token_analysis:
876
  html += f"""
877
  <div style="padding: 8px 16px; background: linear-gradient(135deg, #1e293b, #334155); border: 1px solid #475569; border-radius: 8px; color: #e2e8f0; font-weight: 500; transition: all 0.3s ease;">
 
889
 
890
  <div style="padding: 20px; background: rgba(20, 184, 166, 0.1); border: 1px solid #14b8a6; border-radius: 12px;">
891
  <div style="margin-bottom: 12px; font-weight: 600; color: #14b8a6; display: flex; align-items: center; gap: 8px;">
892
+ Machine View:
893
  </div>
894
  <div style="display: flex; flex-wrap: wrap; gap: 10px;">
895
  """
896
 
 
897
  color_map = {
898
  "prefix": "linear-gradient(135deg, #8b5cf6, #a855f7)",
899
  "suffix": "linear-gradient(135deg, #10b981, #14b8a6)",
 
919
  <div style="margin-top: 24px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;">
920
  """
921
 
 
922
  word_count = len(token_analysis)
923
  token_count = len(full_analysis)
924
  ratio = round(token_count / max(1, word_count), 2)
 
951
  """Generate HTML for full keyword analysis"""
952
  html = f"""
953
  <div style="font-family: 'Inter', sans-serif; padding: 24px; background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border: 1px solid #475569; border-radius: 16px; color: #ffffff;">
954
+ <h2 style="margin-top: 0; margin-bottom: 24px; color: #ffffff; background: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">Keyword DNA Analysis for: {keyword}</h2>
955
 
956
  <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 24px;">
957
  <div style="padding: 20px; background: rgba(6, 182, 212, 0.1); border: 1px solid #06b6d4; border-radius: 12px;">
958
  <h3 style="margin-top: 0; font-size: 18px; color: #06b6d4; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
959
+ Intent Gene
960
  </h3>
961
  <div style="display: flex; justify-content: space-between; margin-bottom: 12px; color: #e2e8f0;">
962
  <span style="font-weight: 500;">Type:</span>
 
973
 
974
  <div style="padding: 20px; background: rgba(139, 92, 246, 0.1); border: 1px solid #8b5cf6; border-radius: 12px;">
975
  <h3 style="margin-top: 0; font-size: 18px; color: #8b5cf6; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
976
+ Evolution Potential
977
  </h3>
978
  <div style="display: flex; justify-content: center; align-items: center; height: 80px;">
979
  <div style="position: relative; width: 80px; height: 80px;">
 
1004
 
1005
  <div style="padding: 20px; background: rgba(20, 184, 166, 0.1); border: 1px solid #14b8a6; border-radius: 12px; margin-bottom: 24px;">
1006
  <h3 style="margin-top: 0; font-size: 18px; color: #14b8a6; display: flex; align-items: center; gap: 8px; margin-bottom: 16px;">
1007
+ Future Mutations
1008
  </h3>
1009
  <div style="display: flex; flex-direction: column; gap: 10px;">
1010
  """
1011
 
 
1012
  for trend in trends:
1013
  html += f"""
1014
  <div style="display: flex; align-items: center; gap: 12px; padding: 8px 0;">
 
1022
  </div>
1023
 
1024
  <h3 style="margin-bottom: 16px; color: #ffffff; display: flex; align-items: center; gap: 8px;">
1025
+ Token Details & Historical Analysis
1026
  </h3>
1027
  """
1028
 
 
1029
  for i, token in enumerate(token_analysis):
1030
  gradient_colors = [
1031
  "linear-gradient(135deg, #06b6d4, #0891b2)",
 
1051
  if token['entityType']:
1052
  html += f"""
1053
  <span style="padding: 4px 12px; background: rgba(139, 92, 246, 0.2); color: #8b5cf6; border: 1px solid #8b5cf6; border-radius: 6px; font-size: 12px; font-weight: 600; display: flex; align-items: center; gap: 4px;">
1054
+ {token['entityType']}
1055
  </span>
1056
  """
1057
 
 
1067
  </div>
1068
 
1069
  <div style="margin-top: 16px;">
1070
+ <div style="font-size: 14px; color: #94a3b8; margin-bottom: 8px; font-weight: 500;">Historical Relevance:</div>
1071
  <div style="border: 1px solid #475569; border-radius: 8px; padding: 16px; background: rgba(15, 23, 42, 0.8);">
1072
  <div style="font-size: 13px; margin-bottom: 8px; color: #e2e8f0;">
1073
  <span style="font-weight: 600; color: #06b6d4;">Origin:</span>
 
1080
  <div style="display: flex; align-items: flex-end; height: 60px; gap: 6px; margin-top: 12px; padding: 8px; background: rgba(6, 182, 212, 0.05); border-radius: 6px;">
1081
  """
1082
 
 
1083
  for period, value in token['historicalData']:
1084
  period_index = token['historicalData'].index((period, value))
1085
  opacity = 0.4 + (period_index * 0.1)
 
1091
  <div style="font-size: 8px; margin-top: 6px; color: #94a3b8; transform: rotate(45deg); transform-origin: top center; white-space: nowrap; font-weight: 500;">
1092
  {period}
1093
  </div>
 
 
 
1094
  </div>
1095
  """
1096
 
 
1122
 
1123
  progress(0.1, desc="Starting analysis...")
1124
 
 
1125
  model_status = load_models(progress)
1126
  if isinstance(model_status, str) and model_status.startswith("Error"):
1127
  return (
 
1135
  )
1136
 
1137
  try:
 
1138
  words = keyword.strip().lower().split()
1139
  progress(0.2, desc="Analyzing tokens...")
1140
 
 
1141
  token_analysis = analyze_token_types(words)
1142
 
1143
  progress(0.3, desc="Running NER...")
 
1144
  try:
1145
  ner_results = ner_pipeline(keyword)
1146
  except Exception as e:
 
1148
  ner_results = []
1149
 
1150
  progress(0.4, desc="Running POS tagging...")
 
1151
  try:
1152
  pos_results = pos_pipeline(keyword)
1153
  except Exception as e:
1154
  print(f"POS error: {str(e)}")
1155
  pos_results = []
1156
 
 
1157
  full_token_analysis = []
1158
  for token in token_analysis:
1159
+ pos_tag = "NOUN"
 
1160
  for pos_result in pos_results:
1161
  if pos_result["word"].lower() == token["text"]:
1162
  pos_tag = pos_result["entity"]
1163
  break
1164
 
 
1165
  entity_type = None
1166
  for ner_result in ner_results:
1167
  if ner_result["word"].lower() == token["text"]:
1168
  entity_type = ner_result["entity"]
1169
  break
1170
 
 
1171
  historical_data = simulate_historical_data(token["text"])
 
 
1172
  origin = generate_origin_data(token["text"])
1173
+ importance = min(95, 60 + (len(token["text"]) * 2))
1174
 
 
 
 
 
 
1175
  if semantic_model is not None:
1176
  try:
 
1177
  prefix_related = [f"about {token['text']}", f"what is {token['text']}", f"how to {token['text']}"]
1178
  synonym_candidates = ["similar", "equivalent", "comparable", "like", "related", "alternative"]
1179
  domain_terms = ["software", "marketing", "business", "science", "education", "technology"]
1180
  comparison_terms = prefix_related + synonym_candidates + domain_terms
1181
 
 
1182
  similarities = get_semantic_similarity(token['text'], comparison_terms)
 
 
1183
  related_terms = [term for term, score in similarities[:3]]
1184
  except Exception as e:
1185
  print(f"Error generating semantic related terms: {str(e)}")
1186
  related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
1187
  else:
 
1188
  related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
1189
 
1190
  full_token_analysis.append({
 
1199
  })
1200
 
1201
  progress(0.5, desc="Analyzing intent...")
 
1202
  try:
1203
  intent_result = intent_classifier(
1204
  keyword,
 
1216
  except Exception as e:
1217
  print(f"Intent classification error: {str(e)}")
1218
  intent_analysis = {
1219
+ "type": "Informational",
1220
  "strength": 70,
1221
  "mutations": ["fallback-variation-1", "fallback-variation-2"]
1222
  }
1223
 
 
1224
  evolution_potential = min(95, 65 + (len(keyword) % 30))
1225
 
 
1226
  trends = [
1227
  "Voice search adaptation",
1228
  "Visual search integration"
1229
  ]
1230
 
 
1231
  base_volume = 1000 + (len(keyword) * 100)
1232
 
 
1233
  if growth_scenario == "Conservative":
1234
  growth_factor = 1.05 + (0.02 * (sum(ord(c) for c in keyword) % 5))
1235
  elif growth_scenario == "Aggressive":
1236
  growth_factor = 1.15 + (0.05 * (sum(ord(c) for c in keyword) % 5))
1237
+ else:
1238
  growth_factor = 1.1 + (0.03 * (sum(ord(c) for c in keyword) % 5))
1239
 
1240
  evolution_data = []
 
1242
  current_volume = base_volume
1243
 
1244
  for month in months:
 
1245
  np.random.seed(sum(ord(c) for c in month + keyword))
1246
  random_factor = 0.9 + (0.2 * np.random.random())
1247
  current_volume *= growth_factor * random_factor
 
1254
  })
1255
 
1256
  progress(0.6, desc="Creating visualizations...")
 
1257
  evolution_chart = create_evolution_chart(evolution_data, forecast_months, growth_scenario)
1258
 
 
1259
  serp_results = None
1260
  ranking_chart = None
1261
  serp_html = None
1262
 
1263
  if get_serp:
1264
  progress(0.7, desc="Fetching SERP data...")
 
1265
  serp_results = simulate_google_serp(keyword)
1266
 
 
1267
  update_ranking_history(keyword, serp_results)
1268
 
1269
  progress(0.8, desc="Creating ranking charts...")
 
1270
  if keyword in ranking_history and len(ranking_history[keyword]) > 0:
1271
  ranking_chart = create_ranking_history_chart(ranking_history[keyword])
1272
 
 
1273
  serp_html = generate_serp_html(keyword, serp_results)
1274
 
 
1275
  token_viz_html = generate_token_visualization_html(token_analysis, full_token_analysis)
1276
 
 
1277
  analysis_html = generate_full_analysis_html(
1278
  keyword,
1279
  full_token_analysis,
 
1282
  trends
1283
  )
1284
 
 
1285
  json_results = {
1286
  "keyword": keyword,
1287
  "tokenAnalysis": full_token_analysis,