PromptMeister commited on
Commit
cd53598
Β·
verified Β·
1 Parent(s): 5e365c0

Update app.py

Browse files

new css interface using aisnipper colors

Files changed (1) hide show
  1. app.py +483 -981
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import gradio as gr
2
  import numpy as np
3
  import pandas as pd
@@ -11,1072 +12,573 @@ import datetime
11
  import plotly.graph_objects as go
12
  from plotly.subplots import make_subplots
13
 
14
- # Global variables to store models
15
- tokenizer = None
16
- ner_pipeline = None
17
- pos_pipeline = None
18
- intent_classifier = None
19
- semantic_model = None
20
- stt_model = None # Speech-to-text model
21
- models_loaded = False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- # Database to store keyword ranking history (in-memory database for this example)
24
- # In a real app, you would use a proper database
25
- ranking_history = {}
 
 
 
 
26
 
27
- def load_models(progress=gr.Progress()):
28
- """Lazy-load models only when needed"""
29
- global tokenizer, ner_pipeline, pos_pipeline, intent_classifier, semantic_model, stt_model, models_loaded
30
-
31
- if models_loaded:
32
- return True
33
-
34
- try:
35
- progress(0.1, desc="Loading models...")
36
-
37
- # Use smaller models and load them sequentially to reduce memory pressure
38
- from transformers import AutoTokenizer, pipeline
39
-
40
- progress(0.2, desc="Loading tokenizer...")
41
- tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased")
42
-
43
- progress(0.3, desc="Loading NER model...")
44
- ner_pipeline = pipeline("ner", model="dslim/bert-base-NER")
45
-
46
- progress(0.4, desc="Loading POS model...")
47
- # Use smaller POS model
48
- from transformers import AutoModelForTokenClassification, BertTokenizerFast
49
- pos_model = AutoModelForTokenClassification.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
50
- pos_tokenizer = BertTokenizerFast.from_pretrained("vblagoje/bert-english-uncased-finetuned-pos")
51
- pos_pipeline = pipeline("token-classification", model=pos_model, tokenizer=pos_tokenizer)
52
-
53
- progress(0.6, desc="Loading intent classifier...")
54
- # Use a smaller model for zero-shot classification
55
- intent_classifier = pipeline(
56
- "zero-shot-classification",
57
- model="typeform/distilbert-base-uncased-mnli", # Smaller than BART
58
- device=0 if torch.cuda.is_available() else -1 # Use GPU if available
59
- )
60
-
61
- progress(0.7, desc="Loading speech-to-text model...")
62
- try:
63
- # Load automatic speech recognition model
64
- from transformers import WhisperProcessor, WhisperForConditionalGeneration
65
- processor = WhisperProcessor.from_pretrained("openai/whisper-small.en")
66
- stt_model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small.en")
67
- stt_model = (processor, stt_model)
68
- except Exception as e:
69
- print(f"Warning: Could not load speech-to-text model: {str(e)}")
70
- stt_model = None # Set to None so we can check if it's available
71
-
72
- progress(0.8, desc="Loading semantic model...")
73
- try:
74
- from sentence_transformers import SentenceTransformer
75
- semantic_model = SentenceTransformer('all-MiniLM-L6-v2')
76
- except Exception as e:
77
- print(f"Warning: Could not load semantic model: {str(e)}")
78
- semantic_model = None # Set to None so we can check if it's available
79
-
80
- progress(1.0, desc="Models loaded successfully!")
81
- models_loaded = True
82
- return True
83
-
84
- except Exception as e:
85
- print(f"Error loading models: {str(e)}")
86
- return f"Error: {str(e)}"
87
 
88
- def speech_to_text(audio_path):
89
- """Convert speech to text using the loaded speech-to-text model"""
90
- if stt_model is None:
91
- return "Speech-to-text model not loaded. Please try text input instead."
92
-
93
- try:
94
- import librosa
95
- import numpy as np
96
-
97
- # Load audio file
98
- audio, sr = librosa.load(audio_path, sr=16000)
99
-
100
- # Process audio with Whisper
101
- processor, model = stt_model
102
- input_features = processor(audio, sampling_rate=16000, return_tensors="pt").input_features
103
-
104
- # Generate token ids
105
- predicted_ids = model.generate(input_features)
106
-
107
- # Decode token ids to text
108
- transcription = processor.batch_decode(predicted_ids, skip_special_tokens=True)[0]
109
-
110
- return transcription
111
- except Exception as e:
112
- print(f"Error in speech_to_text: {str(e)}")
113
- return f"Error processing speech: {str(e)}"
114
 
115
- def handle_voice_input(audio):
116
- """Handle voice input and convert to text"""
117
- if audio is None:
118
- return "No audio detected. Please try again."
119
-
120
- try:
121
- # Convert speech to text
122
- text = speech_to_text(audio)
123
- return text
124
- except Exception as e:
125
- print(f"Error in handle_voice_input: {str(e)}")
126
- return f"Error: {str(e)}"
127
 
128
- def simulate_google_serp(keyword, num_results=10):
129
- """Simulate Google SERP results for a keyword"""
130
- try:
131
- # In a real implementation, this would call the Google API
132
- # For now, we'll generate fake SERP data
133
-
134
- # Deterministic seed for consistent results by keyword
135
- np.random.seed(sum(ord(c) for c in keyword))
136
-
137
- serp_results = []
138
- domains = [
139
- "example.com", "wikipedia.org", "medium.com", "github.com",
140
- "stackoverflow.com", "amazon.com", "youtube.com", "reddit.com",
141
- "linkedin.com", "twitter.com", "facebook.com", "instagram.com"
142
- ]
143
-
144
- for i in range(1, num_results + 1):
145
- domain = domains[i % len(domains)]
146
- title = f"{keyword.title()} - {domain.split('.')[0].title()} Resource #{i}"
147
- snippet = f"This is a simulated SERP result for '{keyword}'. Result #{i} would provide relevant information about this topic."
148
- url = f"https://www.{domain}/{keyword.replace(' ', '-')}-resource-{i}"
149
-
150
- position = i
151
- ctr = round(0.3 * (0.85 ** (i - 1)), 4) # Simulate click-through rate decay
152
-
153
- serp_results.append({
154
- "position": position,
155
- "title": title,
156
- "url": url,
157
- "domain": domain,
158
- "snippet": snippet,
159
- "ctr_estimate": ctr,
160
- "impressions_estimate": np.random.randint(1000, 10000)
161
- })
162
-
163
- return serp_results
164
- except Exception as e:
165
- print(f"Error in simulate_google_serp: {str(e)}")
166
- return []
167
 
168
- def update_ranking_history(keyword, serp_results):
169
- """Update the ranking history for a keyword"""
170
- try:
171
- # Get current timestamp
172
- timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
173
-
174
- # Initialize if keyword not in history
175
- if keyword not in ranking_history:
176
- ranking_history[keyword] = []
177
-
178
- # Add new entry
179
- ranking_history[keyword].append({
180
- "timestamp": timestamp,
181
- "results": serp_results[:5] # Store top 5 results for history
182
- })
183
-
184
- # Keep only last 10 entries for each keyword
185
- if len(ranking_history[keyword]) > 10:
186
- ranking_history[keyword] = ranking_history[keyword][-10:]
187
-
188
- return True
189
- except Exception as e:
190
- print(f"Error in update_ranking_history: {str(e)}")
191
- return False
192
 
193
- def get_semantic_similarity(token, comparison_terms):
194
- """Calculate semantic similarity between a token and comparison terms"""
195
- try:
196
- from sklearn.metrics.pairwise import cosine_similarity
197
-
198
- token_embedding = semantic_model.encode([token])[0]
199
- comparison_embeddings = semantic_model.encode(comparison_terms)
200
-
201
- similarities = []
202
- for i, emb in enumerate(comparison_embeddings):
203
- similarity = cosine_similarity([token_embedding], [emb])[0][0]
204
- similarities.append((comparison_terms[i], float(similarity)))
205
-
206
- return sorted(similarities, key=lambda x: x[1], reverse=True)
207
- except Exception as e:
208
- print(f"Error in semantic similarity: {str(e)}")
209
- # Return dummy data on error
210
- return [(term, 0.5) for term in comparison_terms]
211
 
212
- def get_token_colors(token_type):
213
- colors = {
214
- "prefix": "#D8BFD8", # Light purple
215
- "suffix": "#AEDAA4", # Light green
216
- "stem": "#A4C2F4", # Light blue
217
- "compound_first": "#FFCC80", # Light orange
218
- "compound_second": "#FFCC80", # Light orange
219
- "word": "#E5E5E5" # Light gray
220
- }
221
- return colors.get(token_type, "#E5E5E5")
222
 
223
- def simulate_historical_data(token):
224
- """Generate simulated historical usage data for a token"""
225
- eras = ["1900s", "1950s", "1980s", "2000s", "2010s", "Present"]
226
-
227
- # Different patterns based on token characteristics
228
- if len(token) > 8:
229
- # Possibly a technical term - recent growth
230
- values = [10, 20, 30, 60, 85, 95]
231
- elif token.startswith(("un", "re", "de", "pre")):
232
- # Prefix words tend to be older
233
- values = [45, 50, 60, 70, 75, 80]
234
- else:
235
- # Standard pattern for common words
236
- # Use token hash value modulo instead of hash() directly to avoid different results across runs
237
- base = 50 + (sum(ord(c) for c in token) % 30)
238
- # Use a fixed seed for reproducibility
239
- np.random.seed(sum(ord(c) for c in token))
240
- noise = np.random.normal(0, 5, 6)
241
- values = [max(5, min(95, base + i*5 + n)) for i, n in enumerate(noise)]
242
-
243
- return list(zip(eras, values))
244
 
245
- def generate_origin_data(token):
246
- """Generate simulated origin/etymology data for a token"""
247
- origins = [
248
- {"era": "Ancient", "language": "Latin"},
249
- {"era": "Ancient", "language": "Greek"},
250
- {"era": "Medieval", "language": "Old English"},
251
- {"era": "16th century", "language": "French"},
252
- {"era": "18th century", "language": "Germanic"},
253
- {"era": "19th century", "language": "Anglo-Saxon"},
254
- {"era": "20th century", "language": "Modern English"}
255
- ]
256
-
257
- # Deterministic selection based on the token
258
- index = sum(ord(c) for c in token) % len(origins)
259
- origin = origins[index]
260
-
261
- note = f"First appeared in {origin['era']} texts derived from {origin['language']}."
262
- origin["note"] = note
263
-
264
- return origin
265
 
266
- def analyze_token_types(tokens):
267
- """Identify token types (prefix, suffix, compound, etc.)"""
268
- processed_tokens = []
269
-
270
- prefixes = ["un", "re", "de", "pre", "post", "anti", "pro", "inter", "sub", "super"]
271
- suffixes = ["ing", "ed", "ly", "ment", "tion", "able", "ible", "ness", "ful", "less"]
272
-
273
- for token in tokens:
274
- token_text = token.lower()
275
- token_type = "word"
276
-
277
- # Check for prefixes
278
- for prefix in prefixes:
279
- if token_text.startswith(prefix) and len(token_text) > len(prefix) + 2:
280
- if token_text != prefix: # Make sure the word isn't just the prefix
281
- token_type = "prefix"
282
- break
283
-
284
- # Check for suffixes
285
- if token_type == "word":
286
- for suffix in suffixes:
287
- if token_text.endswith(suffix) and len(token_text) > len(suffix) + 2:
288
- token_type = "suffix"
289
- break
290
-
291
- # Check for compound words (simplified)
292
- if token_type == "word" and len(token_text) > 8:
293
- token_type = "compound_first" # Simplified - in reality would need more analysis
294
-
295
- processed_tokens.append({
296
- "text": token_text,
297
- "type": token_type
298
- })
299
-
300
- return processed_tokens
301
 
302
- def plot_historical_data(historical_data):
303
- """Create a plot of historical usage data, with error handling"""
304
- try:
305
- eras = [item[0] for item in historical_data]
306
- values = [item[1] for item in historical_data]
307
-
308
- plt.figure(figsize=(8, 3))
309
- plt.bar(eras, values, color='skyblue')
310
- plt.title('Historical Usage')
311
- plt.xlabel('Era')
312
- plt.ylabel('Usage Level')
313
- plt.ylim(0, 100)
314
- plt.xticks(rotation=45)
315
- plt.tight_layout()
316
-
317
- return plt
318
- except Exception as e:
319
- print(f"Error in plot_historical_data: {str(e)}")
320
- # Return a simple error plot
321
- plt.figure(figsize=(8, 3))
322
- plt.text(0.5, 0.5, f"Error creating plot: {str(e)}",
323
- horizontalalignment='center', verticalalignment='center')
324
- plt.axis('off')
325
- return plt
326
 
327
- def create_evolution_chart(data, forecast_months=6, growth_scenario="Moderate"):
328
- """Create a simpler chart that's more compatible with Gradio"""
329
- try:
330
- import plotly.graph_objects as go
331
-
332
- # Create a basic figure without subplots
333
- fig = go.Figure()
334
-
335
- # Add main trace for search volume
336
- fig.add_trace(
337
- go.Scatter(
338
- x=[item["month"] for item in data],
339
- y=[item["searchVolume"] for item in data],
340
- name="Search Volume",
341
- line=dict(color="#8884d8", width=3),
342
- mode="lines+markers"
343
- )
344
- )
345
-
346
- # Scale the other metrics to be visible on the same chart
347
- max_volume = max([item["searchVolume"] for item in data])
348
- scale_factor = max_volume / 100
349
-
350
- # Add competition score (scaled)
351
- fig.add_trace(
352
- go.Scatter(
353
- x=[item["month"] for item in data],
354
- y=[item["competitionScore"] * scale_factor for item in data],
355
- name="Competition Score",
356
- line=dict(color="#82ca9d", width=2, dash="dot"),
357
- mode="lines+markers"
358
- )
359
- )
360
-
361
- # Add intent clarity (scaled)
362
- fig.add_trace(
363
- go.Scatter(
364
- x=[item["month"] for item in data],
365
- y=[item["intentClarity"] * scale_factor for item in data],
366
- name="Intent Clarity",
367
- line=dict(color="#ffc658", width=2, dash="dash"),
368
- mode="lines+markers"
369
- )
370
- )
371
-
372
- # Simple layout
373
- fig.update_layout(
374
- title=f"Keyword Evolution Forecast ({growth_scenario} Growth)",
375
- xaxis_title="Month",
376
- yaxis_title="Value",
377
- legend=dict(orientation="h", y=1.1),
378
- height=500
379
- )
380
-
381
- return fig
382
-
383
- except Exception as e:
384
- print(f"Error in chart creation: {str(e)}")
385
- # Fallback to an even simpler chart
386
- fig = go.Figure(data=go.Scatter(x=[1, 2, 3], y=[4, 1, 2]))
387
- fig.update_layout(title="Fallback Chart (Error occurred)")
388
- return fig
389
 
390
- def create_ranking_history_chart(keyword_history):
391
- """Create a chart showing keyword ranking history over time"""
392
- try:
393
- if not keyword_history or len(keyword_history) < 2:
394
- # Not enough data for a meaningful chart
395
- fig = go.Figure()
396
- fig.update_layout(
397
- title="Insufficient Ranking Data",
398
- annotations=[{
399
- "text": "Need at least 2 data points for ranking history",
400
- "showarrow": False,
401
- "font": {"size": 16},
402
- "xref": "paper",
403
- "yref": "paper",
404
- "x": 0.5,
405
- "y": 0.5
406
- }]
407
- )
408
- return fig
409
-
410
- # Create a figure
411
- fig = go.Figure()
412
-
413
- # Extract timestamps and convert to datetime objects
414
- timestamps = [entry["timestamp"] for entry in keyword_history]
415
- dates = [datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") for ts in timestamps]
416
-
417
- # Get unique domains from all results
418
- all_domains = set()
419
- for entry in keyword_history:
420
- for result in entry["results"]:
421
- all_domains.add(result["domain"])
422
-
423
- # Colors for different domains
424
- domain_colors = {}
425
- color_palette = [
426
- "#1f77b4", "#ff7f0e", "#2ca02c", "#d62728", "#9467bd",
427
- "#8c564b", "#e377c2", "#7f7f7f", "#bcbd22", "#17becf"
428
- ]
429
- for i, domain in enumerate(all_domains):
430
- domain_colors[domain] = color_palette[i % len(color_palette)]
431
-
432
- # Track domains and their positions over time
433
- domain_tracking = {domain: {"x": [], "y": [], "text": []} for domain in all_domains}
434
-
435
- for i, entry in enumerate(keyword_history):
436
- for result in entry["results"]:
437
- domain = result["domain"]
438
- position = result["position"]
439
- title = result["title"]
440
-
441
- domain_tracking[domain]["x"].append(dates[i])
442
- domain_tracking[domain]["y"].append(position)
443
- domain_tracking[domain]["text"].append(title)
444
-
445
- # Add traces for each domain
446
- for domain, data in domain_tracking.items():
447
- if len(data["x"]) > 0: # Only add domains that have data
448
- fig.add_trace(
449
- go.Scatter(
450
- x=data["x"],
451
- y=data["y"],
452
- mode="lines+markers",
453
- name=domain,
454
- line=dict(color=domain_colors[domain]),
455
- hovertemplate="%{text}<br>Position: %{y}<br>Date: %{x}<extra></extra>",
456
- text=data["text"],
457
- marker=dict(size=8)
458
- )
459
- )
460
-
461
- # Update layout
462
- fig.update_layout(
463
- title="Keyword Ranking History",
464
- xaxis_title="Date",
465
- yaxis_title="Position",
466
- yaxis=dict(autorange="reversed"), # Invert y-axis so position 1 is on top
467
- hovermode="closest",
468
- height=500
469
- )
470
-
471
- return fig
472
-
473
- except Exception as e:
474
- print(f"Error in create_ranking_history_chart: {str(e)}")
475
- # Return fallback chart
476
- fig = go.Figure()
477
- fig.update_layout(
478
- title="Error Creating Ranking Chart",
479
- annotations=[{
480
- "text": f"Error: {str(e)}",
481
- "showarrow": False,
482
- "font": {"size": 14},
483
- "xref": "paper",
484
- "yref": "paper",
485
- "x": 0.5,
486
- "y": 0.5
487
- }]
488
- )
489
- return fig
490
 
491
- def generate_serp_html(keyword, serp_results):
492
- """Generate HTML for SERP results"""
493
- if not serp_results:
494
- return "<div>No SERP results available</div>"
495
-
496
- html = f"""
497
- <div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #ddd; border-radius: 8px;">
498
- <h2 style="margin-top: 0;">SERP Results for "{keyword}"</h2>
499
-
500
- <div style="background-color: #f5f5f5; padding: 10px; border-radius: 4px; margin-bottom: 20px;">
501
- <div style="color: #666; font-size: 12px;">This is a simulated SERP. In a real application, this would use the Google API.</div>
502
- </div>
503
-
504
- <div class="serp-results" style="display: flex; flex-direction: column; gap: 16px;">
505
- """
506
-
507
- for result in serp_results:
508
- position = result["position"]
509
- title = result["title"]
510
- url = result["url"]
511
- snippet = result["snippet"]
512
- domain = result["domain"]
513
- ctr = result["ctr_estimate"]
514
- impressions = result["impressions_estimate"]
515
-
516
- html += f"""
517
- <div class="serp-result" style="padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px; position: relative;">
518
- <div style="position: absolute; top: -10px; left: -10px; background-color: #4299e1; color: white; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 12px;">
519
- {position}
520
- </div>
521
- <div style="margin-bottom: 5px;">
522
- <a href="#" style="font-size: 18px; color: #1a73e8; text-decoration: none; font-weight: 500;">{title}</a>
523
- </div>
524
- <div style="margin-bottom: 8px; color: #006621; font-size: 14px;">{url}</div>
525
- <div style="color: #4d5156; font-size: 14px;">{snippet}</div>
526
-
527
- <div style="display: flex; margin-top: 10px; font-size: 12px; color: #666;">
528
- <div style="margin-right: 15px;"><span style="font-weight: 500;">CTR:</span> {ctr:.2%}</div>
529
- <div><span style="font-weight: 500;">Est. Impressions:</span> {impressions:,}</div>
530
- </div>
531
- </div>
532
- """
533
-
534
- html += """
535
- </div>
536
- </div>
537
- """
538
-
539
- return html
540
 
541
- def generate_token_visualization_html(token_analysis, full_analysis):
542
- """Generate HTML for token visualization"""
543
- html = """
544
- <div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #ddd; border-radius: 8px;">
545
- <h2 style="margin-top: 0;">Token Visualization</h2>
546
-
547
- <div style="margin-bottom: 20px; padding: 15px; background-color: #f8f9fa; border-radius: 6px;">
548
- <div style="margin-bottom: 8px; font-weight: bold; color: #4a5568;">Human View:</div>
549
- <div style="display: flex; flex-wrap: wrap; gap: 8px;">
550
- """
551
-
552
- # Add human view tokens
553
- for token in token_analysis:
554
- html += f"""
555
- <div style="padding: 6px 12px; background-color: white; border: 1px solid #cbd5e0; border-radius: 4px;">
556
- {token['text']}
557
- </div>
558
- """
559
-
560
- html += """
561
- </div>
562
- </div>
563
-
564
- <div style="text-align: center; margin: 15px 0;">
565
- <span style="font-size: 20px;">↓</span>
566
- </div>
567
-
568
- <div style="padding: 15px; background-color: #f0fff4; border-radius: 6px;">
569
- <div style="margin-bottom: 8px; font-weight: bold; color: #2f855a;">Machine View:</div>
570
- <div style="display: flex; flex-wrap: wrap; gap: 8px;">
571
- """
572
-
573
- # Add machine view tokens
574
- for token in full_analysis:
575
- bg_color = get_token_colors(token["type"])
576
- html += f"""
577
- <div style="padding: 6px 12px; background-color: {bg_color}; border: 1px solid #a0aec0; border-radius: 4px; font-family: monospace;">
578
- {token['token']}
579
- <span style="font-size: 10px; opacity: 0.7; display: block;">{token['type']}</span>
580
- </div>
581
- """
582
-
583
- html += """
584
- </div>
585
- </div>
586
-
587
- <div style="margin-top: 20px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; text-align: center;">
588
- """
589
-
590
- # Add stats
591
- word_count = len(token_analysis)
592
- token_count = len(full_analysis)
593
- ratio = round(token_count / max(1, word_count), 2)
594
-
595
- html += f"""
596
- <div style="background-color: #ebf8ff; padding: 10px; border-radius: 6px;">
597
- <div style="font-size: 24px; font-weight: bold; color: #3182ce;">{word_count}</div>
598
- <div style="font-size: 14px; color: #4299e1;">Words</div>
599
- </div>
600
-
601
- <div style="background-color: #f0fff4; padding: 10px; border-radius: 6px;">
602
- <div style="font-size: 24px; font-weight: bold; color: #38a169;">{token_count}</div>
603
- <div style="font-size: 14px; color: #48bb78;">Tokens</div>
604
- </div>
605
-
606
- <div style="background-color: #faf5ff; padding: 10px; border-radius: 6px;">
607
- <div style="font-size: 24px; font-weight: bold; color: #805ad5;">{ratio}</div>
608
- <div style="font-size: 14px; color: #9f7aea;">Tokens per Word</div>
609
- </div>
610
- """
611
-
612
- html += """
613
- </div>
614
- </div>
615
- """
616
-
617
- return html
618
 
619
- def generate_full_analysis_html(keyword, token_analysis, intent_analysis, evolution_potential, trends):
620
- """Generate HTML for full keyword analysis"""
621
- html = f"""
622
- <div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #ddd; border-radius: 8px;">
623
- <h2 style="margin-top: 0;">Keyword DNA Analysis for: {keyword}</h2>
624
-
625
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 20px;">
626
- <div style="padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px;">
627
- <h3 style="margin-top: 0; font-size: 16px;">Intent Gene</h3>
628
- <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
629
- <span>Type:</span>
630
- <span>{intent_analysis['type']}</span>
631
- </div>
632
- <div style="display: flex; justify-content: space-between; align-items: center;">
633
- <span>Strength:</span>
634
- <div style="width: 120px; height: 8px; background-color: #edf2f7; border-radius: 4px; overflow: hidden;">
635
- <div style="height: 100%; background-color: #48bb78; width: {intent_analysis['strength']}%;"></div>
636
- </div>
637
- </div>
638
- </div>
639
-
640
- <div style="padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px;">
641
- <h3 style="margin-top: 0; font-size: 16px;">Evolution Potential</h3>
642
- <div style="display: flex; justify-content: center; align-items: center; height: 100px;">
643
- <div style="position: relative; width: 100px; height: 100px;">
644
- <div style="position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;">
645
- <span style="font-size: 24px; font-weight: bold;">{evolution_potential}</span>
646
- </div>
647
- <svg width="100" height="100" viewBox="0 0 36 36">
648
- <path
649
- d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"
650
- fill="none"
651
- stroke="#4CAF50"
652
- stroke-width="3"
653
- stroke-dasharray="{evolution_potential}, 100"
654
- />
655
- </svg>
656
- </div>
657
- </div>
658
- </div>
659
- </div>
660
-
661
- <div style="padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 20px;">
662
- <h3 style="margin-top: 0; font-size: 16px;">Future Mutations</h3>
663
- <div style="display: flex; flex-direction: column; gap: 8px;">
664
- """
665
-
666
- # Add trends
667
- for trend in trends:
668
- html += f"""
669
- <div style="display: flex; align-items: center; gap: 8px;">
670
- <span style="color: #48bb78;">β†—</span>
671
- <span>{trend}</span>
672
- </div>
673
- """
674
-
675
- html += """
676
- </div>
677
- </div>
678
-
679
- <h3 style="margin-bottom: 10px;">Token Details & Historical Analysis</h3>
680
- """
681
-
682
- # Add token details
683
- for token in token_analysis:
684
- html += f"""
685
- <div style="padding: 15px; border: 1px solid #e2e8f0; border-radius: 6px; margin-bottom: 15px;">
686
- <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
687
- <div style="display: flex; align-items: center; gap: 8px;">
688
- <span style="font-size: 18px; font-weight: medium;">{token['token']}</span>
689
- <span style="padding: 2px 8px; background-color: #edf2f7; border-radius: 4px; font-size: 12px;">{token['posTag']}</span>
690
- """
691
-
692
- if token['entityType']:
693
- html += f"""
694
- <span style="padding: 2px 8px; background-color: #ebf8ff; color: #3182ce; border-radius: 4px; font-size: 12px; display: flex; align-items: center;">
695
- β“˜ {token['entityType']}
696
- </span>
697
- """
698
-
699
- html += f"""
700
- </div>
701
- <div style="display: flex; align-items: center; gap: 4px;">
702
- <span style="font-size: 12px; color: #718096;">Importance:</span>
703
- <div style="width: 64px; height: 8px; background-color: #edf2f7; border-radius: 4px; overflow: hidden;">
704
- <div style="height: 100%; background-color: #4299e1; width: {token['importance']}%;"></div>
705
- </div>
706
- </div>
707
- </div>
708
-
709
- <div style="margin-top: 15px;">
710
- <div style="font-size: 12px; color: #718096; margin-bottom: 4px;">Historical Relevance:</div>
711
- <div style="border: 1px solid #e2e8f0; border-radius: 4px; padding: 10px; background-color: #f7fafc;">
712
- <div style="font-size: 12px; margin-bottom: 8px;">
713
- <span style="font-weight: 500;">Origin: </span>
714
- <span>{token['origin']['era']}, </span>
715
- <span style="font-style: italic;">{token['origin']['language']}</span>
716
- </div>
717
- <div style="font-size: 12px; margin-bottom: 12px;">{token['origin']['note']}</div>
718
-
719
- <div style="display: flex; align-items: flex-end; height: 50px; gap: 4px; margin-top: 8px;">
720
- """
721
-
722
- # Add historical data bars
723
- for period, value in token['historicalData']:
724
- opacity = 0.3 + (token['historicalData'].index((period, value)) * 0.1)
725
- html += f"""
726
- <div style="display: flex; flex-direction: column; align-items: center; flex: 1;">
727
- <div style="width: 100%; background-color: rgba(66, 153, 225, {opacity}); border-radius: 2px 2px 0 0; height: {max(4, value)}%;"></div>
728
- <div style="font-size: 9px; margin-top: 4px; color: #718096; transform: rotate(45deg); transform-origin: top left; white-space: nowrap;">
729
- {period}
730
- </div>
731
- </div>
732
- """
733
-
734
- html += """
735
- </div>
736
- </div>
737
- </div>
738
- </div>
739
- """
740
-
741
- html += """
742
- </div>
743
- """
744
-
745
- return html
746
 
747
- def analyze_keyword(keyword, forecast_months=6, growth_scenario="Moderate", get_serp=False, progress=gr.Progress()):
748
- """Main function to analyze a keyword"""
749
- if not keyword or not keyword.strip():
750
- return (
751
- "<div>Please enter a keyword to analyze</div>",
752
- "<div>Please enter a keyword to analyze</div>",
753
- None,
754
- None,
755
- None,
756
- None,
757
- None
758
- )
759
-
760
- progress(0.1, desc="Starting analysis...")
761
-
762
- # Load models if not already loaded
763
- model_status = load_models(progress)
764
- if isinstance(model_status, str) and model_status.startswith("Error"):
765
- return (
766
- f"<div style='color:red;'>{model_status}</div>",
767
- f"<div style='color:red;'>{model_status}</div>",
768
- None,
769
- None,
770
- None,
771
- None,
772
- None
773
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
774
 
775
- try:
776
- # Basic tokenization - just split on spaces for simplicity
777
- words = keyword.strip().lower().split()
778
- progress(0.2, desc="Analyzing tokens...")
779
-
780
- # Get token types
781
- token_analysis = analyze_token_types(words)
782
-
783
- progress(0.3, desc="Running NER...")
784
- # Get NER tags - handle potential errors
785
- try:
786
- ner_results = ner_pipeline(keyword)
787
- except Exception as e:
788
- print(f"NER error: {str(e)}")
789
- ner_results = []
790
-
791
- progress(0.4, desc="Running POS tagging...")
792
- # Get POS tags - handle potential errors
793
- try:
794
- pos_results = pos_pipeline(keyword)
795
- except Exception as e:
796
- print(f"POS error: {str(e)}")
797
- pos_results = []
798
-
799
- # Process and organize results
800
- full_token_analysis = []
801
- for token in token_analysis:
802
- # Find POS tag for this token
803
- pos_tag = "NOUN" # Default
804
- for pos_result in pos_results:
805
- if pos_result["word"].lower() == token["text"]:
806
- pos_tag = pos_result["entity"]
807
- break
808
-
809
- # Find entity type if any
810
- entity_type = None
811
- for ner_result in ner_results:
812
- if ner_result["word"].lower() == token["text"]:
813
- entity_type = ner_result["entity"]
814
- break
815
-
816
- # Generate historical data
817
- historical_data = simulate_historical_data(token["text"])
818
-
819
- # Generate origin data
820
- origin = generate_origin_data(token["text"])
821
-
822
- # Calculate importance (simplified algorithm)
823
- importance = 60 + (len(token["text"]) * 2)
824
- importance = min(95, importance)
825
-
826
- # Generate more meaningful related terms using semantic similarity
827
- if semantic_model is not None:
828
- try:
829
- # Generate some potential related terms
830
- prefix_related = [f"about {token['text']}", f"what is {token['text']}", f"how to {token['text']}"]
831
- synonym_candidates = ["similar", "equivalent", "comparable", "like", "related", "alternative"]
832
- domain_terms = ["software", "marketing", "business", "science", "education", "technology"]
833
- comparison_terms = prefix_related + synonym_candidates + domain_terms
834
-
835
- # Get similarities
836
- similarities = get_semantic_similarity(token['text'], comparison_terms)
837
-
838
- # Use top 3 most similar terms
839
- related_terms = [term for term, score in similarities[:3]]
840
- except Exception as e:
841
- print(f"Error generating semantic related terms: {str(e)}")
842
- related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
843
- else:
844
- # Fallback if semantic model isn't loaded
845
- related_terms = [f"{token['text']}-related-1", f"{token['text']}-related-2"]
846
-
847
- full_token_analysis.append({
848
- "token": token["text"],
849
- "type": token["type"],
850
- "posTag": pos_tag,
851
- "entityType": entity_type,
852
- "importance": importance,
853
- "historicalData": historical_data,
854
- "origin": origin,
855
- "relatedTerms": related_terms
856
- })
857
-
858
- progress(0.5, desc="Analyzing intent...")
859
- # Intent analysis - handle potential errors
860
- try:
861
- intent_result = intent_classifier(
862
- keyword,
863
- candidate_labels=["informational", "navigational", "transactional"]
864
- )
865
-
866
- intent_analysis = {
867
- "type": intent_result["labels"][0].capitalize(),
868
- "strength": round(intent_result["scores"][0] * 100),
869
- "mutations": [
870
- f"{intent_result['labels'][0]}-variation-1",
871
- f"{intent_result['labels'][0]}-variation-2"
872
- ]
873
- }
874
- except Exception as e:
875
- print(f"Intent classification error: {str(e)}")
876
- intent_analysis = {
877
- "type": "Informational", # Default fallback
878
- "strength": 70,
879
- "mutations": ["fallback-variation-1", "fallback-variation-2"]
880
- }
881
-
882
- # Evolution potential (simplified calculation)
883
- evolution_potential = min(95, 65 + (len(keyword) % 30))
884
-
885
- # Predicted trends (simplified)
886
- trends = [
887
- "Voice search adaptation",
888
- "Visual search integration"
889
- ]
890
-
891
- # Generate more realistic and keyword-specific evolution data
892
- base_volume = 1000 + (len(keyword) * 100)
893
-
894
- # Adjust growth factor based on scenario
895
- if growth_scenario == "Conservative":
896
- growth_factor = 1.05 + (0.02 * (sum(ord(c) for c in keyword) % 5))
897
- elif growth_scenario == "Aggressive":
898
- growth_factor = 1.15 + (0.05 * (sum(ord(c) for c in keyword) % 5))
899
- else: # Moderate
900
- growth_factor = 1.1 + (0.03 * (sum(ord(c) for c in keyword) % 5))
901
-
902
- evolution_data = []
903
- months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][:int(forecast_months)]
904
- current_volume = base_volume
905
-
906
- for month in months:
907
- # Add some randomness to make it look more realistic
908
- np.random.seed(sum(ord(c) for c in month + keyword))
909
- random_factor = 0.9 + (0.2 * np.random.random())
910
- current_volume *= growth_factor * random_factor
911
-
912
- evolution_data.append({
913
- "month": month,
914
- "searchVolume": int(current_volume),
915
- "competitionScore": min(95, 45 + (months.index(month) * 3) + (sum(ord(c) for c in keyword) % 10)),
916
- "intentClarity": min(95, 80 + (months.index(month) * 2) + (sum(ord(c) for c in keyword) % 5))
917
- })
918
-
919
- progress(0.6, desc="Creating visualizations...")
920
- # Create interactive evolution chart
921
- evolution_chart = create_evolution_chart(evolution_data, forecast_months, growth_scenario)
922
-
923
- # SERP results and ranking history (new feature)
924
- serp_results = None
925
- ranking_chart = None
926
- serp_html = None
927
-
928
- if get_serp:
929
- progress(0.7, desc="Fetching SERP data...")
930
- # Get SERP results
931
- serp_results = simulate_google_serp(keyword)
932
-
933
- # Update ranking history
934
- update_ranking_history(keyword, serp_results)
935
-
936
- progress(0.8, desc="Creating ranking charts...")
937
- # Create ranking history chart
938
- if keyword in ranking_history and len(ranking_history[keyword]) > 0:
939
- ranking_chart = create_ranking_history_chart(ranking_history[keyword])
940
-
941
- # Generate SERP HTML
942
- serp_html = generate_serp_html(keyword, serp_results)
943
-
944
- # Generate HTML for token visualization
945
- token_viz_html = generate_token_visualization_html(token_analysis, full_token_analysis)
946
-
947
- # Generate HTML for full analysis
948
- analysis_html = generate_full_analysis_html(
949
- keyword,
950
- full_token_analysis,
951
- intent_analysis,
952
- evolution_potential,
953
- trends
954
- )
955
-
956
- # Generate JSON results
957
- json_results = {
958
- "keyword": keyword,
959
- "tokenAnalysis": full_token_analysis,
960
- "intentAnalysis": intent_analysis,
961
- "evolutionPotential": evolution_potential,
962
- "predictedTrends": trends,
963
- "forecast": {
964
- "months": forecast_months,
965
- "scenario": growth_scenario,
966
- "data": evolution_data
967
- },
968
- "serpResults": serp_results
969
- }
970
-
971
- progress(1.0, desc="Analysis complete!")
972
- return token_viz_html, analysis_html, json_results, evolution_chart, serp_html, ranking_chart, keyword
973
 
974
- except Exception as e:
975
- error_message = f"<div style='color:red;padding:20px;'>Error analyzing keyword: {str(e)}</div>"
976
- print(f"Error in analyze_keyword: {str(e)}")
977
- return error_message, error_message, None, None, None, None, None
 
 
 
 
 
 
 
 
 
 
 
 
 
978
 
979
- # Create the Gradio interface
980
- with gr.Blocks(css="footer {visibility: hidden}") as demo:
981
- gr.Markdown("# Keyword DNA Analyzer")
982
- gr.Markdown("Analyze the linguistic DNA of your keywords to understand their structure, intent, and potential.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
983
 
984
  with gr.Row():
985
  with gr.Column(scale=1):
986
- # Add voice search capabilities
987
  with gr.Group():
988
- gr.Markdown("### Enter Keyword")
989
  with gr.Row():
990
- input_text = gr.Textbox(label="Enter keyword to analyze", placeholder="e.g. artificial intelligence")
 
 
 
 
991
 
992
  with gr.Row():
993
- audio_input = gr.Audio(type="filepath", label="Or use voice search")
994
- voice_submit_btn = gr.Button("Convert Voice to Text", variant="secondary")
 
 
 
 
 
 
995
 
996
- # Add SERP settings
997
- with gr.Accordion("Analysis Settings", open=False):
998
  with gr.Row():
999
- forecast_months = gr.Slider(minimum=3, maximum=12, value=6, step=1, label="Forecast Months")
1000
- include_serp = gr.Checkbox(label="Include SERP Analysis", value=True)
 
 
 
 
 
 
 
 
 
1001
 
1002
  growth_scenario = gr.Radio(
1003
  ["Conservative", "Moderate", "Aggressive"],
1004
  value="Moderate",
1005
- label="Growth Scenario"
1006
  )
1007
 
1008
- # Add loading indicator
1009
- status_html = gr.HTML('<div style="color:gray;text-align:center;">Enter a keyword and click "Analyze DNA"</div>')
 
 
1010
 
1011
- analyze_btn = gr.Button("Analyze DNA", variant="primary")
 
 
 
 
 
1012
 
1013
- with gr.Row():
 
 
1014
  example_btns = []
1015
- for example in ["preprocessing", "breakdown", "artificial intelligence", "transformer model", "machine learning"]:
1016
- example_btns.append(gr.Button(example))
 
 
 
 
 
 
 
1017
 
1018
  with gr.Column(scale=2):
1019
  with gr.Tabs():
1020
- with gr.Tab("Token Visualization"):
1021
  token_viz_html = gr.HTML()
1022
 
1023
- with gr.Tab("Full Analysis"):
1024
  analysis_html = gr.HTML()
1025
 
1026
- with gr.Tab("Evolution Chart"):
1027
  evolution_chart = gr.Plot(label="Keyword Evolution Forecast")
1028
 
1029
- with gr.Tab("SERP Results"):
1030
  serp_html = gr.HTML()
1031
 
1032
- with gr.Tab("Ranking History"):
1033
  ranking_chart = gr.Plot(label="Keyword Ranking History")
1034
 
1035
- with gr.Tab("Raw Data"):
1036
  json_output = gr.JSON()
1037
 
1038
- # Voice to text conversion handler
1039
  voice_submit_btn.click(
1040
  handle_voice_input,
1041
  inputs=[audio_input],
1042
  outputs=[input_text]
1043
  )
1044
 
1045
- # Set up event handlers
1046
  analyze_btn.click(
1047
- lambda: '<div style="color:blue;text-align:center;">Loading models and analyzing... This may take a moment.</div>',
1048
  outputs=status_html
1049
  ).then(
1050
  analyze_keyword,
1051
  inputs=[input_text, forecast_months, growth_scenario, include_serp],
1052
  outputs=[token_viz_html, analysis_html, json_output, evolution_chart, serp_html, ranking_chart, input_text]
1053
  ).then(
1054
- lambda: '<div style="color:green;text-align:center;">Analysis complete!</div>',
1055
  outputs=status_html
1056
  )
1057
 
1058
- # Example buttons
1059
  for btn in example_btns:
1060
- # Define the function that will be called when an example button is clicked
1061
  def set_example(btn_label):
1062
- return btn_label
 
1063
 
1064
  btn.click(
1065
  set_example,
1066
  inputs=[btn],
1067
  outputs=[input_text]
1068
  ).then(
1069
- lambda: '<div style="color:blue;text-align:center;">Loading models and analyzing... This may take a moment.</div>',
1070
  outputs=status_html
1071
  ).then(
1072
  analyze_keyword,
1073
  inputs=[input_text, forecast_months, growth_scenario, include_serp],
1074
  outputs=[token_viz_html, analysis_html, json_output, evolution_chart, serp_html, ranking_chart, input_text]
1075
  ).then(
1076
- lambda: '<div style="color:green;text-align:center;">Analysis complete!</div>',
1077
  outputs=status_html
1078
  )
1079
 
1080
- # Launch the app
1081
  if __name__ == "__main__":
1082
- demo.launch()
 
 
 
 
 
1
+ # Your existing imports remain the same
2
  import gradio as gr
3
  import numpy as np
4
  import pandas as pd
 
12
  import plotly.graph_objects as go
13
  from plotly.subplots import make_subplots
14
 
15
+ # AI Snipper Custom CSS
16
+ ai_snipper_css = """
17
+ /* AI Snipper Color Variables */
18
+ :root {
19
+ --ai-cyan: #06b6d4;
20
+ --ai-blue: #3b82f6;
21
+ --ai-purple: #8b5cf6;
22
+ --ai-teal: #14b8a6;
23
+ --ai-indigo: #6366f1;
24
+
25
+ --bg-primary: #0a0e16;
26
+ --bg-secondary: #1a2332;
27
+ --bg-tertiary: #2a3441;
28
+ --bg-card: #1e293b;
29
+ --bg-card-hover: #334155;
30
+
31
+ --text-primary: #ffffff;
32
+ --text-secondary: #e2e8f0;
33
+ --text-muted: #94a3b8;
34
+ --text-accent: #06b6d4;
35
+
36
+ --border-primary: #475569;
37
+ --border-accent: #06b6d4;
38
+
39
+ --gradient-primary: linear-gradient(135deg, #06b6d4, #3b82f6, #8b5cf6);
40
+ --gradient-secondary: linear-gradient(135deg, #0a0e16 0%, #1a2332 50%, #2a3441 100%);
41
+ --gradient-button: linear-gradient(135deg, #06b6d4, #3b82f6);
42
+ --gradient-card: linear-gradient(135deg, #1e293b 0%, #334155 100%);
43
+ }
44
 
45
+ /* Main container styling */
46
+ .gradio-container {
47
+ background: var(--gradient-secondary) !important;
48
+ color: var(--text-primary) !important;
49
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
50
+ min-height: 100vh !important;
51
+ }
52
 
53
+ /* Header styling */
54
+ .gradio-container h1 {
55
+ background: var(--gradient-primary) !important;
56
+ -webkit-background-clip: text !important;
57
+ -webkit-text-fill-color: transparent !important;
58
+ background-clip: text !important;
59
+ text-align: center !important;
60
+ font-size: 3rem !important;
61
+ font-weight: 800 !important;
62
+ margin-bottom: 1rem !important;
63
+ text-shadow: 0 0 20px rgba(6, 182, 212, 0.3) !important;
64
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
+ .gradio-container h2, .gradio-container h3 {
67
+ color: var(--text-primary) !important;
68
+ font-weight: 600 !important;
69
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
+ /* Card/Panel styling */
72
+ .gr-box, .gr-form, .gr-panel {
73
+ background: var(--gradient-card) !important;
74
+ border: 1px solid var(--border-primary) !important;
75
+ border-radius: 16px !important;
76
+ box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1) !important;
77
+ backdrop-filter: blur(10px) !important;
78
+ }
 
 
 
 
79
 
80
+ /* Group styling */
81
+ .gr-group {
82
+ background: var(--gradient-card) !important;
83
+ border: 1px solid var(--border-primary) !important;
84
+ border-radius: 12px !important;
85
+ padding: 1.5rem !important;
86
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
 
88
+ /* Text inputs */
89
+ .gr-textbox textarea, .gr-textbox input {
90
+ background: var(--bg-secondary) !important;
91
+ border: 1px solid var(--border-primary) !important;
92
+ border-radius: 12px !important;
93
+ color: var(--text-primary) !important;
94
+ padding: 1rem !important;
95
+ font-family: inherit !important;
96
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ .gr-textbox textarea:focus, .gr-textbox input:focus {
99
+ border-color: var(--border-accent) !important;
100
+ box-shadow: 0 0 0 3px rgba(6, 182, 212, 0.1) !important;
101
+ outline: none !important;
102
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ .gr-textbox textarea::placeholder, .gr-textbox input::placeholder {
105
+ color: var(--text-muted) !important;
106
+ }
 
 
 
 
 
 
 
107
 
108
+ /* Labels */
109
+ .gr-textbox label, .gr-slider label, .gr-radio label, .gr-checkbox label {
110
+ color: var(--text-secondary) !important;
111
+ font-weight: 500 !important;
112
+ margin-bottom: 0.5rem !important;
113
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
+ /* Buttons */
116
+ .gr-button {
117
+ background: var(--gradient-button) !important;
118
+ border: none !important;
119
+ border-radius: 12px !important;
120
+ color: var(--text-primary) !important;
121
+ font-weight: 600 !important;
122
+ padding: 1rem 2rem !important;
123
+ transition: all 0.3s ease !important;
124
+ box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1) !important;
125
+ font-family: inherit !important;
126
+ }
 
 
 
 
 
 
 
 
127
 
128
+ .gr-button:hover {
129
+ transform: translateY(-2px) !important;
130
+ box-shadow: 0 10px 20px rgba(6, 182, 212, 0.3) !important;
131
+ filter: brightness(1.1) !important;
132
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
+ .gr-button.secondary {
135
+ background: var(--bg-card) !important;
136
+ color: var(--text-secondary) !important;
137
+ border: 1px solid var(--border-primary) !important;
138
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ .gr-button.secondary:hover {
141
+ background: var(--bg-card-hover) !important;
142
+ border-color: var(--border-accent) !important;
143
+ color: var(--text-primary) !important;
144
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
+ /* File upload areas */
147
+ .gr-file-upload {
148
+ background: var(--bg-card) !important;
149
+ border: 2px dashed var(--border-accent) !important;
150
+ border-radius: 16px !important;
151
+ color: var(--text-secondary) !important;
152
+ transition: all 0.3s ease !important;
153
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
 
155
+ .gr-file-upload:hover {
156
+ border-color: var(--ai-cyan) !important;
157
+ background: var(--bg-card-hover) !important;
158
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
 
160
+ /* Audio input */
161
+ .gr-audio {
162
+ background: var(--gradient-card) !important;
163
+ border: 1px solid var(--border-primary) !important;
164
+ border-radius: 12px !important;
165
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
166
 
167
+ /* Sliders */
168
+ .gr-slider input[type="range"] {
169
+ background: var(--bg-secondary) !important;
170
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
171
 
172
+ .gr-slider input[type="range"]::-webkit-slider-track {
173
+ background: var(--bg-secondary) !important;
174
+ border-radius: 6px !important;
175
+ }
176
+
177
+ .gr-slider input[type="range"]::-webkit-slider-thumb {
178
+ background: var(--gradient-button) !important;
179
+ border: none !important;
180
+ border-radius: 50% !important;
181
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
182
+ }
183
+
184
+ /* Radio buttons and checkboxes */
185
+ .gr-radio input[type="radio"] {
186
+ accent-color: var(--ai-cyan) !important;
187
+ }
188
+
189
+ .gr-checkbox input[type="checkbox"] {
190
+ accent-color: var(--ai-cyan) !important;
191
+ }
192
+
193
+ /* Tabs */
194
+ .gr-tab-nav {
195
+ background: var(--gradient-card) !important;
196
+ border-radius: 12px !important;
197
+ padding: 0.5rem !important;
198
+ }
199
+
200
+ .gr-tab-nav button {
201
+ background: transparent !important;
202
+ color: var(--text-secondary) !important;
203
+ border: none !important;
204
+ border-radius: 8px !important;
205
+ margin: 0 4px !important;
206
+ padding: 0.75rem 1.5rem !important;
207
+ transition: all 0.3s ease !important;
208
+ font-weight: 500 !important;
209
+ }
210
+
211
+ .gr-tab-nav button.selected {
212
+ background: var(--gradient-button) !important;
213
+ color: var(--text-primary) !important;
214
+ box-shadow: 0 2px 4px rgba(6, 182, 212, 0.3) !important;
215
+ }
216
+
217
+ .gr-tab-nav button:hover:not(.selected) {
218
+ background: var(--bg-card-hover) !important;
219
+ color: var(--text-primary) !important;
220
+ }
221
+
222
+ /* Tab content */
223
+ .gr-tabitem {
224
+ background: var(--gradient-card) !important;
225
+ border: 1px solid var(--border-primary) !important;
226
+ border-radius: 12px !important;
227
+ padding: 1.5rem !important;
228
+ margin-top: 1rem !important;
229
+ }
230
+
231
+ /* Progress bars */
232
+ .gr-progress {
233
+ background: var(--bg-secondary) !important;
234
+ border-radius: 6px !important;
235
+ }
236
+
237
+ .gr-progress-bar {
238
+ background: var(--gradient-button) !important;
239
+ border-radius: 6px !important;
240
+ }
241
+
242
+ /* Accordion */
243
+ .gr-accordion {
244
+ background: var(--gradient-card) !important;
245
+ border: 1px solid var(--border-primary) !important;
246
+ border-radius: 12px !important;
247
+ }
248
+
249
+ .gr-accordion summary {
250
+ background: var(--bg-card) !important;
251
+ color: var(--text-primary) !important;
252
+ padding: 1rem !important;
253
+ border-radius: 12px !important;
254
+ cursor: pointer !important;
255
+ font-weight: 600 !important;
256
+ }
257
+
258
+ .gr-accordion[open] summary {
259
+ border-bottom: 1px solid var(--border-primary) !important;
260
+ border-radius: 12px 12px 0 0 !important;
261
+ }
262
+
263
+ /* JSON output */
264
+ .gr-json {
265
+ background: var(--bg-secondary) !important;
266
+ border: 1px solid var(--border-primary) !important;
267
+ border-radius: 12px !important;
268
+ color: var(--text-primary) !important;
269
+ }
270
+
271
+ /* HTML output areas */
272
+ .gr-html {
273
+ background: var(--gradient-card) !important;
274
+ border: 1px solid var(--border-primary) !important;
275
+ border-radius: 12px !important;
276
+ padding: 1rem !important;
277
+ }
278
+
279
+ /* Plot containers */
280
+ .gr-plot {
281
+ background: var(--gradient-card) !important;
282
+ border: 1px solid var(--border-primary) !important;
283
+ border-radius: 12px !important;
284
+ padding: 1rem !important;
285
+ }
286
+
287
+ /* Rows and columns */
288
+ .gr-row {
289
+ gap: 1.5rem !important;
290
+ }
291
+
292
+ .gr-column {
293
+ gap: 1rem !important;
294
+ }
295
+
296
+ /* Scrollbars */
297
+ ::-webkit-scrollbar {
298
+ width: 8px;
299
+ height: 8px;
300
+ }
301
+
302
+ ::-webkit-scrollbar-track {
303
+ background: var(--bg-secondary);
304
+ border-radius: 4px;
305
+ }
306
+
307
+ ::-webkit-scrollbar-thumb {
308
+ background: var(--gradient-button);
309
+ border-radius: 4px;
310
+ }
311
+
312
+ ::-webkit-scrollbar-thumb:hover {
313
+ background: var(--ai-cyan);
314
+ }
315
+
316
+ /* Custom DNA-themed elements */
317
+ .dna-header {
318
+ position: relative;
319
+ text-align: center;
320
+ padding: 2rem 0;
321
+ margin-bottom: 2rem;
322
+ }
323
+
324
+ .dna-header::before {
325
+ content: '';
326
+ position: absolute;
327
+ top: 0;
328
+ left: 50%;
329
+ transform: translateX(-50%);
330
+ width: 100px;
331
+ height: 4px;
332
+ background: var(--gradient-primary);
333
+ border-radius: 2px;
334
+ }
335
+
336
+ .dna-subtitle {
337
+ color: var(--text-muted) !important;
338
+ font-size: 1.2rem !important;
339
+ margin-top: 1rem !important;
340
+ font-weight: 400 !important;
341
+ }
342
+
343
+ /* Example button styling */
344
+ .example-buttons .gr-button {
345
+ background: var(--bg-card) !important;
346
+ color: var(--text-accent) !important;
347
+ border: 1px solid var(--border-accent) !important;
348
+ font-size: 0.875rem !important;
349
+ padding: 0.5rem 1rem !important;
350
+ }
351
+
352
+ .example-buttons .gr-button:hover {
353
+ background: var(--gradient-button) !important;
354
+ color: var(--text-primary) !important;
355
+ border-color: transparent !important;
356
+ }
357
+
358
+ /* Status messages */
359
+ .status-message {
360
+ text-align: center !important;
361
+ padding: 1rem !important;
362
+ border-radius: 8px !important;
363
+ margin: 1rem 0 !important;
364
+ font-weight: 500 !important;
365
+ }
366
+
367
+ .status-loading {
368
+ background: rgba(6, 182, 212, 0.1) !important;
369
+ border: 1px solid var(--border-accent) !important;
370
+ color: var(--text-accent) !important;
371
+ }
372
+
373
+ .status-success {
374
+ background: rgba(20, 184, 166, 0.1) !important;
375
+ border: 1px solid var(--ai-teal) !important;
376
+ color: var(--ai-teal) !important;
377
+ }
378
+
379
+ .status-error {
380
+ background: rgba(239, 68, 68, 0.1) !important;
381
+ border: 1px solid #ef4444 !important;
382
+ color: #ef4444 !important;
383
+ }
384
+
385
+ /* Footer hiding */
386
+ footer {
387
+ visibility: hidden !important;
388
+ }
389
+
390
+ /* Mobile responsiveness */
391
+ @media (max-width: 768px) {
392
+ .gradio-container h1 {
393
+ font-size: 2rem !important;
394
+ }
395
 
396
+ .gr-button {
397
+ width: 100% !important;
398
+ justify-content: center !important;
399
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
400
 
401
+ .gr-row {
402
+ flex-direction: column !important;
403
+ }
404
+ }
405
+ """
406
+
407
+ # Keep all your existing function code exactly the same
408
+ # [Your existing global variables and all functions remain unchanged]
409
+
410
+ # Global variables to store models
411
+ tokenizer = None
412
+ ner_pipeline = None
413
+ pos_pipeline = None
414
+ intent_classifier = None
415
+ semantic_model = None
416
+ stt_model = None # Speech-to-text model
417
+ models_loaded = False
418
 
419
+ # Database to store keyword ranking history (in-memory database for this example)
420
+ ranking_history = {}
421
+
422
+ # [Keep all your existing functions - load_models, speech_to_text, etc.]
423
+ # I'm not repeating them here to save space, but they should remain exactly the same
424
+
425
+ # Updated Gradio interface with AI Snipper styling
426
+ with gr.Blocks(
427
+ css=ai_snipper_css,
428
+ title="🧬 AI Snipper Keyword DNA Analyzer",
429
+ theme=gr.themes.Base(
430
+ primary_hue="cyan",
431
+ secondary_hue="blue",
432
+ neutral_hue="slate",
433
+ font=[gr.themes.GoogleFont("Inter"), "sans-serif"]
434
+ )
435
+ ) as demo:
436
+
437
+ # Custom header with DNA theme
438
+ gr.HTML("""
439
+ <div class="dna-header">
440
+ <h1>🧬 Keyword DNA Analyzer</h1>
441
+ <p class="dna-subtitle">
442
+ Decode the genetic structure of your keywords with AI-powered analysis
443
+ </p>
444
+ </div>
445
+ """)
446
 
447
  with gr.Row():
448
  with gr.Column(scale=1):
449
+ # Voice search capabilities with improved styling
450
  with gr.Group():
451
+ gr.Markdown("### 🎯 Enter Keyword")
452
  with gr.Row():
453
+ input_text = gr.Textbox(
454
+ label="Keyword to analyze",
455
+ placeholder="e.g. artificial intelligence, machine learning, SEO strategy...",
456
+ lines=2
457
+ )
458
 
459
  with gr.Row():
460
+ audio_input = gr.Audio(
461
+ type="filepath",
462
+ label="🎀 Or use voice search"
463
+ )
464
+ voice_submit_btn = gr.Button(
465
+ "πŸ”„ Convert Voice to Text",
466
+ variant="secondary"
467
+ )
468
 
469
+ # SERP settings with better organization
470
+ with gr.Accordion("βš™οΈ Analysis Settings", open=False):
471
  with gr.Row():
472
+ forecast_months = gr.Slider(
473
+ minimum=3,
474
+ maximum=12,
475
+ value=6,
476
+ step=1,
477
+ label="πŸ“ˆ Forecast Months"
478
+ )
479
+ include_serp = gr.Checkbox(
480
+ label="πŸ” Include SERP Analysis",
481
+ value=True
482
+ )
483
 
484
  growth_scenario = gr.Radio(
485
  ["Conservative", "Moderate", "Aggressive"],
486
  value="Moderate",
487
+ label="πŸ“Š Growth Scenario"
488
  )
489
 
490
+ # Status indicator with custom styling
491
+ status_html = gr.HTML(
492
+ '<div class="status-message">πŸš€ Enter a keyword and click "Analyze DNA" to begin</div>'
493
+ )
494
 
495
+ # Main analyze button
496
+ analyze_btn = gr.Button(
497
+ "🧬 Analyze DNA",
498
+ variant="primary",
499
+ size="lg"
500
+ )
501
 
502
+ # Example buttons with custom styling
503
+ gr.Markdown("### πŸ’‘ Try These Examples")
504
+ with gr.Row(elem_classes="example-buttons"):
505
  example_btns = []
506
+ examples = [
507
+ "preprocessing",
508
+ "breakdown",
509
+ "artificial intelligence",
510
+ "transformer model",
511
+ "machine learning"
512
+ ]
513
+ for example in examples:
514
+ example_btns.append(gr.Button(f"✨ {example}"))
515
 
516
  with gr.Column(scale=2):
517
  with gr.Tabs():
518
+ with gr.Tab("πŸ”¬ Token Visualization"):
519
  token_viz_html = gr.HTML()
520
 
521
+ with gr.Tab("πŸ“Š Full Analysis"):
522
  analysis_html = gr.HTML()
523
 
524
+ with gr.Tab("πŸ“ˆ Evolution Chart"):
525
  evolution_chart = gr.Plot(label="Keyword Evolution Forecast")
526
 
527
+ with gr.Tab("🌐 SERP Results"):
528
  serp_html = gr.HTML()
529
 
530
+ with gr.Tab("πŸ“‰ Ranking History"):
531
  ranking_chart = gr.Plot(label="Keyword Ranking History")
532
 
533
+ with gr.Tab("πŸ’Ύ Raw Data"):
534
  json_output = gr.JSON()
535
 
536
+ # Event handlers remain the same but with updated status messages
537
  voice_submit_btn.click(
538
  handle_voice_input,
539
  inputs=[audio_input],
540
  outputs=[input_text]
541
  )
542
 
543
+ # Updated status messages with custom styling
544
  analyze_btn.click(
545
+ lambda: '<div class="status-message status-loading">πŸ”„ Loading models and analyzing... This may take a moment.</div>',
546
  outputs=status_html
547
  ).then(
548
  analyze_keyword,
549
  inputs=[input_text, forecast_months, growth_scenario, include_serp],
550
  outputs=[token_viz_html, analysis_html, json_output, evolution_chart, serp_html, ranking_chart, input_text]
551
  ).then(
552
+ lambda: '<div class="status-message status-success">βœ… Analysis complete! Check the results above.</div>',
553
  outputs=status_html
554
  )
555
 
556
+ # Example buttons with enhanced interaction
557
  for btn in example_btns:
 
558
  def set_example(btn_label):
559
+ # Remove the emoji prefix for the actual keyword
560
+ return btn_label.replace("✨ ", "")
561
 
562
  btn.click(
563
  set_example,
564
  inputs=[btn],
565
  outputs=[input_text]
566
  ).then(
567
+ lambda: '<div class="status-message status-loading">πŸ”„ Loading models and analyzing... This may take a moment.</div>',
568
  outputs=status_html
569
  ).then(
570
  analyze_keyword,
571
  inputs=[input_text, forecast_months, growth_scenario, include_serp],
572
  outputs=[token_viz_html, analysis_html, json_output, evolution_chart, serp_html, ranking_chart, input_text]
573
  ).then(
574
+ lambda: '<div class="status-message status-success">βœ… Analysis complete! Check the results above.</div>',
575
  outputs=status_html
576
  )
577
 
578
+ # Launch configuration
579
  if __name__ == "__main__":
580
+ demo.launch(
581
+ share=True,
582
+ show_error=True,
583
+ debug=True
584
+ )