blazingbunny commited on
Commit
d95747f
·
verified ·
1 Parent(s): 573eea2

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +335 -335
app.py CHANGED
@@ -1,335 +1,335 @@
1
- import gradio as gr
2
- import google.generativeai as genai
3
- from bs4 import BeautifulSoup, NavigableString
4
- import re
5
- import json
6
- import random
7
- import os
8
-
9
- # --- Constants & Config ---
10
- BLACKLIST_WORDS = [
11
- "landscape", "realm", "navigate", "unveil", "explore", "transformative",
12
- "encompass", "examine", "crucial", "discover", "dive", "delve",
13
- "uncover", "unlock", "elevate", "unleash", "harness"
14
- ]
15
-
16
- BRITISH_MAPPINGS = {
17
- "color": "colour", "flavor": "flavour", "humor": "humour", "labor": "labour",
18
- "neighbor": "neighbour", "favor": "favour", "honor": "honour", "behavior": "behaviour",
19
- "center": "centre", "fiber": "fibre", "liter": "litre", "theater": "theatre",
20
- "meter": "metre", "analyze": "analyse", "breathalyze": "breathalyse", "paralyze": "paralyse",
21
- "catalyze": "catalyse", "organization": "organisation", "realize": "realise",
22
- "recognize": "recognise", "standardize": "standardise", "appetizer": "appetiser",
23
- "leukemia": "leukaemia", "maneuver": "manoeuvre", "estrogen": "oestrogen",
24
- "pediatric": "paediatric", "defense": "defence", "license": "licence",
25
- "offense": "offence", "pretense": "pretence", "traveler": "traveller", "modeling": "modelling",
26
- "cancelled": "cancelled",
27
- "program": "programme",
28
- }
29
-
30
- SOCIAL_PROOF_TEMPLATES = [
31
- "We recently hired {KEYWORD} for our project, and the results were outstanding. The team was professional, efficient, and delivered exactly what we needed. I highly recommend their services to anyone looking for reliable {KEYWORD_LOWER}.",
32
- "I was struggling to find trustworthy {KEYWORD_LOWER} until I found this company. They exceeded my expectations with their attention to detail and timely completion. It was a refreshing experience to work with such dedicated professionals.",
33
- "If you need {KEYWORD_LOWER}, look no further. Their expertise is evident in the quality of their work, and the customer service is top-notch. I am completely satisfied with the outcome and will definitely use them again.",
34
- "Finding a dependable {KEYWORD} can be difficult, but this team made it easy. They communicated clearly throughout the process and finished the job to a high standard. I'm very impressed with their workmanship."
35
- ]
36
-
37
- # --- Logic Ports ---
38
-
39
- def capitalize(s):
40
- if not s: return ""
41
- return s[0].upper() + s[1:]
42
-
43
- def parse_growmatic_data(text):
44
- term_map = {}
45
- if not text: return term_map
46
- # Regex to match: "term": number% OR term: number%
47
- regex = r'["\']?([\w\s]+)["\']?\s*[:=]\s*(\d+)%?'
48
- matches = re.findall(regex, text)
49
- for term, score in matches:
50
- term_lower = term.strip().lower()
51
- if term_lower:
52
- term_map[term_lower] = int(score)
53
- return term_map
54
-
55
- def generate_titles(main_keyword, term_map):
56
- titles = []
57
- # Templates
58
- templates = [
59
- "{KEYWORD} in [location] - {TERM_A} [zip]",
60
- "{KEYWORD} in [location] - {TERM_B} Services [zip]",
61
- "Expert {KEYWORD} in [location] - {TERM_C} [zip]",
62
- "{KEYWORD} Services in [location] - {TERM_A} [zip]",
63
- "Leading {KEYWORD} in [location] - {TERM_B} [zip]",
64
- "{KEYWORD} Specialists in [location] - {TERM_C} [zip]",
65
- "Best {KEYWORD} in [location] - {TERM_A} Solutions [zip]"
66
- ]
67
-
68
- # Sort terms by score descending
69
- sorted_terms = sorted(term_map.keys(), key=lambda k: term_map[k], reverse=True)
70
-
71
- term_a = sorted_terms[0] if len(sorted_terms) > 0 else "Projects"
72
- term_b = sorted_terms[1] if len(sorted_terms) > 1 else "Installations"
73
- term_c = sorted_terms[2] if len(sorted_terms) > 2 else "Solutions"
74
-
75
- for tmpl in templates:
76
- t = tmpl.replace("{KEYWORD}", main_keyword)
77
- t = t.replace("{TERM_A}", capitalize(term_a))
78
- t = t.replace("{TERM_B}", capitalize(term_b))
79
- t = t.replace("{TERM_C}", capitalize(term_c))
80
- titles.append(t)
81
-
82
- # Variations
83
- variations = [
84
- f"{main_keyword} {capitalize(term_a)}",
85
- f"{main_keyword} {capitalize(term_b)} Services",
86
- f"{capitalize(term_a)} & {main_keyword}"
87
- ]
88
- return titles + variations
89
-
90
- def calculate_score(title, term_map):
91
- title_lower = title.lower()
92
-
93
- # Blacklist check
94
- for bad_word in BLACKLIST_WORDS:
95
- if bad_word in title_lower:
96
- return {"title": title, "score": 0, "terms": "BLACKLISTED"}
97
-
98
- total_score = 0
99
- matched_terms = []
100
-
101
- for term, weight in term_map.items():
102
- if term in title_lower:
103
- total_score += weight
104
- matched_terms.append(f"{term} ({weight}%)")
105
-
106
- # Scale score (approx 0-10)
107
- final_score = round(total_score / 30, 1)
108
- if final_score > 10: final_score = 10
109
-
110
- return {
111
- "title": title,
112
- "score": final_score,
113
- "terms": ", ".join(matched_terms)
114
- }
115
-
116
- def process_text_nodes(html_content, callback):
117
- if not html_content: return ""
118
- soup = BeautifulSoup(html_content, 'html.parser')
119
-
120
- # Recursive function specifically for NavigableStrings
121
- def walk(node):
122
- if isinstance(node, NavigableString):
123
- if node.parent.name not in ['script', 'style']: # Skip script/style tags
124
- new_text = callback(str(node))
125
- if new_text != str(node):
126
- node.replace_with(new_text)
127
- elif hasattr(node, 'children'):
128
- for child in node.children:
129
- walk(child)
130
-
131
- walk(soup)
132
- return str(soup)
133
-
134
- def convert_to_british(html_content):
135
- if not html_content: return ""
136
-
137
- def replacer(text):
138
- processed = text
139
- for us, uk in BRITISH_MAPPINGS.items():
140
- # Regex for whole word match, case insensitive
141
- pattern = re.compile(r'\b' + re.escape(us) + r'\b', re.IGNORECASE)
142
-
143
- def match_handler(m):
144
- # Preserve case
145
- word = m.group(0)
146
- if word[0].isupper():
147
- return capitalize(uk)
148
- return uk
149
-
150
- processed = pattern.sub(match_handler, processed)
151
- return processed
152
-
153
- return process_text_nodes(html_content, replacer)
154
-
155
- def clean_homepage_content(html_content):
156
- if not html_content: return ""
157
-
158
- def replacer(text):
159
- clean = text
160
-
161
- # 1. Remove phrases
162
- phrases_to_remove = [
163
- r'\s+in\s+\[location\]', r'in\s+\[location\]',
164
- r'\s+across\s+the\s+\[location\]', r'across\s+the\s+\[location\]',
165
- r'\s+across\s+\[location\]', r'across\s+\[location\]',
166
- r'\s+around\s+the\s+\[location\]', r'around\s+the\s+\[location\]',
167
- r'\s+nearby\s+\[location\]', r'nearby\s+\[location\]',
168
- r'\s+throughout\s+\[location\]', r'throughout\s+\[location\]'
169
- ]
170
- for phrase in phrases_to_remove:
171
- clean = re.sub(phrase, '', clean, flags=re.IGNORECASE)
172
-
173
- # 2. Remove tags
174
- tags_to_remove = [
175
- r'\[location\]', r'\[county\]', r'\[region\]', r'\[zip\]'
176
- ]
177
- for tag in tags_to_remove:
178
- clean = re.sub(tag, '', clean, flags=re.IGNORECASE)
179
-
180
- # 3. Footer text
181
- footer_regex = r'in\s*\[region\]\.?\s*Here\s*are\s*some\s*towns\s*we\s*cover\s*near\s*\[location\]\s*\[zip\]\s*\[cities[^\]]*\]'
182
- clean = re.sub(footer_regex, '', clean, flags=re.IGNORECASE | re.DOTALL)
183
-
184
- # 4. Whitespace cleanup
185
- clean = re.sub(r'\s{2,}', ' ', clean)
186
- clean = re.sub(r'\s+\.', '.', clean)
187
- clean = re.sub(r'\s+\?', '?', clean)
188
- clean = re.sub(r'\s+\,', ',', clean)
189
-
190
- return clean.strip()
191
-
192
- return process_text_nodes(html_content, replacer)
193
-
194
-
195
- # --- Gemini Integration ---
196
-
197
- def call_gemini(prompt, api_key, model_name="gemini-1.5-flash"):
198
- if not api_key: return None
199
- try:
200
- genai.configure(api_key=api_key)
201
- model = genai.GenerativeModel(model_name)
202
- response = model.generate_content(prompt)
203
- return response.text
204
- except Exception as e:
205
- return f"Error: {str(e)}"
206
-
207
- # --- Main Automation Logic ---
208
-
209
- def run_automation(main_keyword, site_link, growmatic_data, api_key, article_content, model_selection):
210
- if not main_keyword:
211
- return "Error: Main Keyword is required.", ""
212
-
213
- term_map = parse_growmatic_data(growmatic_data)
214
-
215
- # 1. Magic Page Logic
216
- magic_output_html = ""
217
-
218
- # SEO Titles
219
- if api_key:
220
- # LLM Title Gen
221
- terms_str = ", ".join([f"{k} ({v}%)" for k, v in term_map.items()])
222
- prompt = f"""Act as an SEO expert.
223
- Main Keyword: "{main_keyword}"
224
- Semantic Terms (Growmatic Data): {terms_str}
225
-
226
- Task:
227
- 1. Generate 3 highly optimized Meta Titles for a page targeting "{main_keyword}". Use the semantic terms to increase relevance.
228
- 2. Generate a list of 5-8 Meta Keywords (comma separated).
229
- 3. Select the "Best" Title from the 3 options based on SEO scoring principles.
230
-
231
- Output JSON format ONLY (no markdown):
232
- {{
233
- "metaTitles": ["Title 1", "Title 2", "Title 3"],
234
- "bestTitle": "The Best Title",
235
- "metaKeywords": "keyword1, keyword2, keyword3"
236
- }}"""
237
-
238
- llm_resp = call_gemini(prompt, api_key, model_selection)
239
-
240
- try:
241
- # Clean json block if present
242
- clean_json = llm_resp.replace('```json', '').replace('```', '').strip()
243
- data = json.loads(clean_json)
244
-
245
- magic_output_html += "<h3>--- GENERATED SEO TITLES (LLM) ---</h3>"
246
- for t in data.get("metaTitles", []):
247
- is_best = t == data.get("bestTitle")
248
- style = "color: blue; font-weight: bold;" if is_best else ""
249
- suffix = "(Best Match)" if is_best else ""
250
- magic_output_html += f'<p style="{style}">• {t} {suffix}</p>'
251
-
252
- magic_output_html += f"<p><strong>Meta Keywords:</strong> {data.get('metaKeywords', '')}</p><br>"
253
-
254
- except:
255
- magic_output_html += f"<p style='color:red'>Error parsing LLM response: {llm_resp}</p>"
256
-
257
- else:
258
- # Template Gen
259
- titles = generate_titles(main_keyword, term_map)
260
- scored = [calculate_score(t, term_map) for t in titles]
261
- scored.sort(key=lambda x: x['score'], reverse=True)
262
-
263
- magic_output_html += "<h3>--- GENERATED SEO TITLES (Template) ---</h3>"
264
- for item in scored[:5]:
265
- magic_output_html += f"<p>• [Score: {item['score']}] {item['title']}</p>"
266
- magic_output_html += "<br>"
267
-
268
- # Social Proof
269
- social_proof_text = ""
270
- if api_key:
271
- sp_prompt = f"""Write 2 positive testimonials for a service provider offering "{main_keyword}".
272
- Create two very non-generic names including last names.
273
- Each testimonial should be max 3-4 sentences.
274
- Focus on professionalism, result quality, and ease of working with them."""
275
- social_proof_text = call_gemini(sp_prompt, api_key, model_selection)
276
- else:
277
- tmpl = random.choice(SOCIAL_PROOF_TEMPLATES)
278
- social_proof_text = tmpl.replace("{KEYWORD}", main_keyword).replace("{KEYWORD_LOWER}", main_keyword.lower())
279
-
280
- magic_output_html += f"<h3>--- MAGIC PAGE METADATA ---</h3>"
281
- magic_output_html += f"<p><strong>Target Keyword:</strong> {main_keyword}</p>"
282
- magic_output_html += f"<p><strong>Site URL:</strong> {site_link}</p><br>"
283
-
284
- magic_output_html += f"<h3>--- SOCIAL PROOF ---</h3>"
285
- magic_output_html += f"<p>{social_proof_text.replace(chr(10), '<br>')}</p>"
286
-
287
- # 2. Homepage Logic
288
- clean_html = clean_homepage_content(article_content)
289
- british_html = convert_to_british(clean_html)
290
-
291
- return magic_output_html, british_html
292
-
293
-
294
- # --- Gradio UI ---
295
-
296
- with gr.Blocks(title="Content Automation Tool") as app:
297
- gr.Markdown("# Content Automation Tool (Gradio Edition)")
298
- gr.Markdown("Generate Magic Page & Optimized Homepage Content Instantly")
299
-
300
- with gr.Row():
301
- with gr.Column():
302
- main_keyword = gr.Textbox(label="Main Keyword", placeholder="e.g. Suspended Ceiling Contractors")
303
- site_link = gr.Textbox(label="Site Link", placeholder="e.g. https://example.com")
304
- growmatic_data = gr.TextArea(label="Growmatic Data", placeholder='"suspended": 100%, "ceiling": 73%')
305
-
306
- with gr.Row():
307
- api_key = gr.Textbox(label="Gemini API Key", type="password", placeholder="AIza...")
308
- model_selection = gr.Dropdown(
309
- choices=["gemini-1.5-flash", "gemini-1.5-pro", "gemini-1.0-pro"],
310
- value="gemini-1.5-flash",
311
- label="Gemini Model"
312
- )
313
-
314
- with gr.Column():
315
- article_content = gr.Textbox(label="Article Content (HTML/Text)", lines=15, placeholder="Paste content with [tags] here...")
316
-
317
- generate_btn = gr.Button("Generate Output ✨", variant="primary")
318
-
319
- with gr.Row():
320
- with gr.Column():
321
- gr.Markdown("### Magic Page Output")
322
- magic_output = gr.HTML(label="Magic Page Result")
323
-
324
- with gr.Column():
325
- gr.Markdown("### Homepage Output")
326
- home_output = gr.HTML(label="Homepage Result")
327
-
328
- generate_btn.click(
329
- fn=run_automation,
330
- inputs=[main_keyword, site_link, growmatic_data, api_key, article_content, model_selection],
331
- outputs=[magic_output, home_output]
332
- )
333
-
334
- if __name__ == "__main__":
335
- app.launch()
 
1
+ import gradio as gr
2
+ import google.generativeai as genai
3
+ from bs4 import BeautifulSoup, NavigableString
4
+ import re
5
+ import json
6
+ import random
7
+ import os
8
+
9
+ # --- Constants & Config ---
10
+ BLACKLIST_WORDS = [
11
+ "landscape", "realm", "navigate", "unveil", "explore", "transformative",
12
+ "encompass", "examine", "crucial", "discover", "dive", "delve",
13
+ "uncover", "unlock", "elevate", "unleash", "harness"
14
+ ]
15
+
16
+ BRITISH_MAPPINGS = {
17
+ "color": "colour", "flavor": "flavour", "humor": "humour", "labor": "labour",
18
+ "neighbor": "neighbour", "favor": "favour", "honor": "honour", "behavior": "behaviour",
19
+ "center": "centre", "fiber": "fibre", "liter": "litre", "theater": "theatre",
20
+ "meter": "metre", "analyze": "analyse", "breathalyze": "breathalyse", "paralyze": "paralyse",
21
+ "catalyze": "catalyse", "organization": "organisation", "realize": "realise",
22
+ "recognize": "recognise", "standardize": "standardise", "appetizer": "appetiser",
23
+ "leukemia": "leukaemia", "maneuver": "manoeuvre", "estrogen": "oestrogen",
24
+ "pediatric": "paediatric", "defense": "defence", "license": "licence",
25
+ "offense": "offence", "pretense": "pretence", "traveler": "traveller", "modeling": "modelling",
26
+ "cancelled": "cancelled",
27
+ "program": "programme",
28
+ }
29
+
30
+ SOCIAL_PROOF_TEMPLATES = [
31
+ "We recently hired {KEYWORD} for our project, and the results were outstanding. The team was professional, efficient, and delivered exactly what we needed. I highly recommend their services to anyone looking for reliable {KEYWORD_LOWER}.",
32
+ "I was struggling to find trustworthy {KEYWORD_LOWER} until I found this company. They exceeded my expectations with their attention to detail and timely completion. It was a refreshing experience to work with such dedicated professionals.",
33
+ "If you need {KEYWORD_LOWER}, look no further. Their expertise is evident in the quality of their work, and the customer service is top-notch. I am completely satisfied with the outcome and will definitely use them again.",
34
+ "Finding a dependable {KEYWORD} can be difficult, but this team made it easy. They communicated clearly throughout the process and finished the job to a high standard. I'm very impressed with their workmanship."
35
+ ]
36
+
37
+ # --- Logic Ports ---
38
+
39
+ def capitalize(s):
40
+ if not s: return ""
41
+ return s[0].upper() + s[1:]
42
+
43
+ def parse_growmatic_data(text):
44
+ term_map = {}
45
+ if not text: return term_map
46
+ # Regex to match: "term": number% OR term: number%
47
+ regex = r'["\']?([\w\s]+)["\']?\s*[:=]\s*(\d+)%?'
48
+ matches = re.findall(regex, text)
49
+ for term, score in matches:
50
+ term_lower = term.strip().lower()
51
+ if term_lower:
52
+ term_map[term_lower] = int(score)
53
+ return term_map
54
+
55
+ def generate_titles(main_keyword, term_map):
56
+ titles = []
57
+ # Templates
58
+ templates = [
59
+ "{KEYWORD} in [location] - {TERM_A} [zip]",
60
+ "{KEYWORD} in [location] - {TERM_B} Services [zip]",
61
+ "Expert {KEYWORD} in [location] - {TERM_C} [zip]",
62
+ "{KEYWORD} Services in [location] - {TERM_A} [zip]",
63
+ "Leading {KEYWORD} in [location] - {TERM_B} [zip]",
64
+ "{KEYWORD} Specialists in [location] - {TERM_C} [zip]",
65
+ "Best {KEYWORD} in [location] - {TERM_A} Solutions [zip]"
66
+ ]
67
+
68
+ # Sort terms by score descending
69
+ sorted_terms = sorted(term_map.keys(), key=lambda k: term_map[k], reverse=True)
70
+
71
+ term_a = sorted_terms[0] if len(sorted_terms) > 0 else "Projects"
72
+ term_b = sorted_terms[1] if len(sorted_terms) > 1 else "Installations"
73
+ term_c = sorted_terms[2] if len(sorted_terms) > 2 else "Solutions"
74
+
75
+ for tmpl in templates:
76
+ t = tmpl.replace("{KEYWORD}", main_keyword)
77
+ t = t.replace("{TERM_A}", capitalize(term_a))
78
+ t = t.replace("{TERM_B}", capitalize(term_b))
79
+ t = t.replace("{TERM_C}", capitalize(term_c))
80
+ titles.append(t)
81
+
82
+ # Variations
83
+ variations = [
84
+ f"{main_keyword} {capitalize(term_a)}",
85
+ f"{main_keyword} {capitalize(term_b)} Services",
86
+ f"{capitalize(term_a)} & {main_keyword}"
87
+ ]
88
+ return titles + variations
89
+
90
+ def calculate_score(title, term_map):
91
+ title_lower = title.lower()
92
+
93
+ # Blacklist check
94
+ for bad_word in BLACKLIST_WORDS:
95
+ if bad_word in title_lower:
96
+ return {"title": title, "score": 0, "terms": "BLACKLISTED"}
97
+
98
+ total_score = 0
99
+ matched_terms = []
100
+
101
+ for term, weight in term_map.items():
102
+ if term in title_lower:
103
+ total_score += weight
104
+ matched_terms.append(f"{term} ({weight}%)")
105
+
106
+ # Scale score (approx 0-10)
107
+ final_score = round(total_score / 30, 1)
108
+ if final_score > 10: final_score = 10
109
+
110
+ return {
111
+ "title": title,
112
+ "score": final_score,
113
+ "terms": ", ".join(matched_terms)
114
+ }
115
+
116
+ def process_text_nodes(html_content, callback):
117
+ if not html_content: return ""
118
+ soup = BeautifulSoup(html_content, 'html.parser')
119
+
120
+ # Recursive function specifically for NavigableStrings
121
+ def walk(node):
122
+ if isinstance(node, NavigableString):
123
+ if node.parent.name not in ['script', 'style']: # Skip script/style tags
124
+ new_text = callback(str(node))
125
+ if new_text != str(node):
126
+ node.replace_with(new_text)
127
+ elif hasattr(node, 'children'):
128
+ for child in node.children:
129
+ walk(child)
130
+
131
+ walk(soup)
132
+ return str(soup)
133
+
134
+ def convert_to_british(html_content):
135
+ if not html_content: return ""
136
+
137
+ def replacer(text):
138
+ processed = text
139
+ for us, uk in BRITISH_MAPPINGS.items():
140
+ # Regex for whole word match, case insensitive
141
+ pattern = re.compile(r'\b' + re.escape(us) + r'\b', re.IGNORECASE)
142
+
143
+ def match_handler(m):
144
+ # Preserve case
145
+ word = m.group(0)
146
+ if word[0].isupper():
147
+ return capitalize(uk)
148
+ return uk
149
+
150
+ processed = pattern.sub(match_handler, processed)
151
+ return processed
152
+
153
+ return process_text_nodes(html_content, replacer)
154
+
155
+ def clean_homepage_content(html_content):
156
+ if not html_content: return ""
157
+
158
+ def replacer(text):
159
+ clean = text
160
+
161
+ # 1. Remove phrases
162
+ phrases_to_remove = [
163
+ r'\s+in\s+\[location\]', r'in\s+\[location\]',
164
+ r'\s+across\s+the\s+\[location\]', r'across\s+the\s+\[location\]',
165
+ r'\s+across\s+\[location\]', r'across\s+\[location\]',
166
+ r'\s+around\s+the\s+\[location\]', r'around\s+the\s+\[location\]',
167
+ r'\s+nearby\s+\[location\]', r'nearby\s+\[location\]',
168
+ r'\s+throughout\s+\[location\]', r'throughout\s+\[location\]'
169
+ ]
170
+ for phrase in phrases_to_remove:
171
+ clean = re.sub(phrase, '', clean, flags=re.IGNORECASE)
172
+
173
+ # 2. Remove tags
174
+ tags_to_remove = [
175
+ r'\[location\]', r'\[county\]', r'\[region\]', r'\[zip\]'
176
+ ]
177
+ for tag in tags_to_remove:
178
+ clean = re.sub(tag, '', clean, flags=re.IGNORECASE)
179
+
180
+ # 3. Footer text
181
+ footer_regex = r'in\s*\[region\]\.?\s*Here\s*are\s*some\s*towns\s*we\s*cover\s*near\s*\[location\]\s*\[zip\]\s*\[cities[^\]]*\]'
182
+ clean = re.sub(footer_regex, '', clean, flags=re.IGNORECASE | re.DOTALL)
183
+
184
+ # 4. Whitespace cleanup
185
+ clean = re.sub(r'\s{2,}', ' ', clean)
186
+ clean = re.sub(r'\s+\.', '.', clean)
187
+ clean = re.sub(r'\s+\?', '?', clean)
188
+ clean = re.sub(r'\s+\,', ',', clean)
189
+
190
+ return clean.strip()
191
+
192
+ return process_text_nodes(html_content, replacer)
193
+
194
+
195
+ # --- Gemini Integration ---
196
+
197
+ def call_gemini(prompt, api_key, model_name="gemini-2.5-flash"):
198
+ if not api_key: return None
199
+ try:
200
+ genai.configure(api_key=api_key)
201
+ model = genai.GenerativeModel(model_name)
202
+ response = model.generate_content(prompt)
203
+ return response.text
204
+ except Exception as e:
205
+ return f"Error: {str(e)}"
206
+
207
+ # --- Main Automation Logic ---
208
+
209
+ def run_automation(main_keyword, site_link, growmatic_data, api_key, article_content, model_selection):
210
+ if not main_keyword:
211
+ return "Error: Main Keyword is required.", ""
212
+
213
+ term_map = parse_growmatic_data(growmatic_data)
214
+
215
+ # 1. Magic Page Logic
216
+ magic_output_html = ""
217
+
218
+ # SEO Titles
219
+ if api_key:
220
+ # LLM Title Gen
221
+ terms_str = ", ".join([f"{k} ({v}%)" for k, v in term_map.items()])
222
+ prompt = f"""Act as an SEO expert.
223
+ Main Keyword: "{main_keyword}"
224
+ Semantic Terms (Growmatic Data): {terms_str}
225
+
226
+ Task:
227
+ 1. Generate 3 highly optimized Meta Titles for a page targeting "{main_keyword}". Use the semantic terms to increase relevance.
228
+ 2. Generate a list of 5-8 Meta Keywords (comma separated).
229
+ 3. Select the "Best" Title from the 3 options based on SEO scoring principles.
230
+
231
+ Output JSON format ONLY (no markdown):
232
+ {{
233
+ "metaTitles": ["Title 1", "Title 2", "Title 3"],
234
+ "bestTitle": "The Best Title",
235
+ "metaKeywords": "keyword1, keyword2, keyword3"
236
+ }}"""
237
+
238
+ llm_resp = call_gemini(prompt, api_key, model_selection)
239
+
240
+ try:
241
+ # Clean json block if present
242
+ clean_json = llm_resp.replace('```json', '').replace('```', '').strip()
243
+ data = json.loads(clean_json)
244
+
245
+ magic_output_html += "<h3>--- GENERATED SEO TITLES (LLM) ---</h3>"
246
+ for t in data.get("metaTitles", []):
247
+ is_best = t == data.get("bestTitle")
248
+ style = "color: blue; font-weight: bold;" if is_best else ""
249
+ suffix = "(Best Match)" if is_best else ""
250
+ magic_output_html += f'<p style="{style}">• {t} {suffix}</p>'
251
+
252
+ magic_output_html += f"<p><strong>Meta Keywords:</strong> {data.get('metaKeywords', '')}</p><br>"
253
+
254
+ except:
255
+ magic_output_html += f"<p style='color:red'>Error parsing LLM response: {llm_resp}</p>"
256
+
257
+ else:
258
+ # Template Gen
259
+ titles = generate_titles(main_keyword, term_map)
260
+ scored = [calculate_score(t, term_map) for t in titles]
261
+ scored.sort(key=lambda x: x['score'], reverse=True)
262
+
263
+ magic_output_html += "<h3>--- GENERATED SEO TITLES (Template) ---</h3>"
264
+ for item in scored[:5]:
265
+ magic_output_html += f"<p>• [Score: {item['score']}] {item['title']}</p>"
266
+ magic_output_html += "<br>"
267
+
268
+ # Social Proof
269
+ social_proof_text = ""
270
+ if api_key:
271
+ sp_prompt = f"""Write 2 positive testimonials for a service provider offering "{main_keyword}".
272
+ Create two very non-generic names including last names.
273
+ Each testimonial should be max 3-4 sentences.
274
+ Focus on professionalism, result quality, and ease of working with them."""
275
+ social_proof_text = call_gemini(sp_prompt, api_key, model_selection)
276
+ else:
277
+ tmpl = random.choice(SOCIAL_PROOF_TEMPLATES)
278
+ social_proof_text = tmpl.replace("{KEYWORD}", main_keyword).replace("{KEYWORD_LOWER}", main_keyword.lower())
279
+
280
+ magic_output_html += f"<h3>--- MAGIC PAGE METADATA ---</h3>"
281
+ magic_output_html += f"<p><strong>Target Keyword:</strong> {main_keyword}</p>"
282
+ magic_output_html += f"<p><strong>Site URL:</strong> {site_link}</p><br>"
283
+
284
+ magic_output_html += f"<h3>--- SOCIAL PROOF ---</h3>"
285
+ magic_output_html += f"<p>{social_proof_text.replace(chr(10), '<br>')}</p>"
286
+
287
+ # 2. Homepage Logic
288
+ clean_html = clean_homepage_content(article_content)
289
+ british_html = convert_to_british(clean_html)
290
+
291
+ return magic_output_html, british_html
292
+
293
+
294
+ # --- Gradio UI ---
295
+
296
+ with gr.Blocks(title="Content Automation Tool") as app:
297
+ gr.Markdown("# Content Automation Tool (Gradio Edition)")
298
+ gr.Markdown("Generate Magic Page & Optimized Homepage Content Instantly")
299
+
300
+ with gr.Row():
301
+ with gr.Column():
302
+ main_keyword = gr.Textbox(label="Main Keyword", placeholder="e.g. Suspended Ceiling Contractors")
303
+ site_link = gr.Textbox(label="Site Link", placeholder="e.g. https://example.com")
304
+ growmatic_data = gr.TextArea(label="Growmatic Data", placeholder='"suspended": 100%, "ceiling": 73%')
305
+
306
+ with gr.Row():
307
+ api_key = gr.Textbox(label="Gemini API Key", type="password", placeholder="AIza...")
308
+ model_selection = gr.Dropdown(
309
+ choices=["gemini-2.5-flash", "gemini-2.5-flash-lite", "gemini-2.5-pro"],
310
+ value="gemini-2.5-pro",
311
+ label="Gemini Model"
312
+ )
313
+
314
+ with gr.Column():
315
+ article_content = gr.Textbox(label="Article Content (HTML/Text)", lines=15, placeholder="Paste content with [tags] here...")
316
+
317
+ generate_btn = gr.Button("Generate Output ✨", variant="primary")
318
+
319
+ with gr.Row():
320
+ with gr.Column():
321
+ gr.Markdown("### Magic Page Output")
322
+ magic_output = gr.HTML(label="Magic Page Result")
323
+
324
+ with gr.Column():
325
+ gr.Markdown("### Homepage Output")
326
+ home_output = gr.HTML(label="Homepage Result")
327
+
328
+ generate_btn.click(
329
+ fn=run_automation,
330
+ inputs=[main_keyword, site_link, growmatic_data, api_key, article_content, model_selection],
331
+ outputs=[magic_output, home_output]
332
+ )
333
+
334
+ if __name__ == "__main__":
335
+ app.launch()