kacapower commited on
Commit
d1d7ef1
Β·
verified Β·
1 Parent(s): 8f2bfe4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +162 -148
app.py CHANGED
@@ -1,31 +1,33 @@
1
  import gradio as gr
2
- from google_play_scraper import search, app
3
  import os
4
  import time
5
  from datetime import datetime
6
  import json
7
- import shutil
8
 
9
  from huggingface_hub import HfApi, hf_hub_download, create_repo
10
 
11
  # -----------------------------
12
- # CONFIG & HF CONNECTION
13
  # -----------------------------
14
  HF_TOKEN = os.getenv("HF_TOKEN")
15
- APP_PASSWORD = os.getenv("APP_PASSWORD") # Set this in your hf Space Secrets
 
16
  DATASET_REPO = "kacapower/lexical-space-data"
17
  THEME_FILE_NAME = "website_theme.xml"
18
 
 
 
 
19
  def save_theme_to_dataset(xml_file):
20
  """Saves the uploaded XML file directly to the private hf dataset."""
21
  if xml_file is None:
22
  return "❌ Please upload an XML file first."
23
 
24
  try:
25
- # Safely get the file path
26
  file_path = xml_file if isinstance(xml_file, str) else xml_file.name
27
 
28
- # NEW: Automatically create the dataset repository if it doesn't exist
29
  try:
30
  create_repo(
31
  repo_id=DATASET_REPO,
@@ -37,7 +39,6 @@ def save_theme_to_dataset(xml_file):
37
  except Exception as repo_err:
38
  return f"❌ Failed to access or create repository: {repo_err}. Check your HF_TOKEN permissions."
39
 
40
- # Upload directly using HfApi
41
  api = HfApi()
42
  api.upload_file(
43
  path_or_fileobj=file_path,
@@ -52,18 +53,18 @@ def save_theme_to_dataset(xml_file):
52
  except Exception as e:
53
  return f"❌ Error saving theme to dataset: {e}"
54
 
 
55
  def generate_blogger_html(app_query, custom_link, mod_features, theme_color, telegram_link, custom_size, custom_date):
 
56
  try:
57
- # 1. Agent searches the Play Store for the app
58
  search_results = search(app_query, lang='en', country='us')
59
  if not search_results:
60
- return "Error: Could not find this app on the Play Store.", "", "0", "0"
61
 
62
- # 2. Extract the exact App ID and fetch full details
63
  app_id = search_results[0]['appId']
64
- app_details = app(app_id, lang='en', country='us')
65
 
66
- # 3. Parse the extended data
67
  title = app_details.get('title', app_query)
68
  icon_url = app_details.get('icon', '')
69
  rating = round(app_details.get('score', 4.5), 1)
@@ -73,117 +74,77 @@ def generate_blogger_html(app_query, custom_link, mod_features, theme_color, tel
73
  installs = app_details.get('installs', '10,000+')
74
  version = app_details.get('version', 'Varies with device')
75
 
76
- # Use custom size or scraped size
77
  scraped_size = app_details.get('size', 'Varies')
78
- final_size = custom_size if custom_size else scraped_size
79
 
80
- # Format the updated timestamp or use custom override
81
  updated_ts = app_details.get('updated', 0)
82
  scraped_date = datetime.fromtimestamp(updated_ts).strftime('%B %d, %Y') if updated_ts else "Recently"
83
- updated_date = custom_date if custom_date else scraped_date
84
 
85
- # Use full description for better SEO and longer content
86
  description = app_details.get('summary', 'Premium application with enhanced features.')
87
  full_description = app_details.get('descriptionHTML', description)
88
 
89
- # 4. Format the custom Mod Features into an HTML list
 
 
 
 
90
  features_list = ""
91
  for feature in mod_features.split(','):
92
  if feature.strip():
93
  features_list += f'<li style="margin-bottom: 8px;"><i class="fa-solid fa-check" style="color: {theme_color}; margin-right: 10px;"></i> {feature.strip()}</li>\n'
94
 
95
- # 5. Build the SEO-Optimized Schemas (App + FAQ + SysReq)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  schema_app = {
97
- "@context": "https://schema.org",
98
- "@type": "SoftwareApplication",
99
- "name": title,
100
- "operatingSystem": "ANDROID",
101
- "applicationCategory": category,
102
- "image": icon_url,
103
- "author": {
104
- "@type": "Organization",
105
- "name": developer
106
- },
107
- "aggregateRating": {
108
- "@type": "AggregateRating",
109
- "ratingValue": str(rating),
110
- "ratingCount": str(reviews_count)
111
- },
112
- "offers": {
113
- "@type": "Offer",
114
- "price": "0.00",
115
- "priceCurrency": "USD"
116
- }
117
  }
118
-
119
- # Generate FAQ data from the app title
120
  schema_faq = {
121
- "@context": "https://schema.org",
122
- "@type": "FAQPage",
123
  "mainEntity": [
124
- {
125
- "@type": "Question",
126
- "name": f"Is {title} Mod APK safe?",
127
- "acceptedAnswer": {
128
- "@type": "Answer",
129
- "text": "Yes, our mods are thoroughly scanned and tested on physical devices before being published to the community."
130
- }
131
- },
132
- {
133
- "@type": "Question",
134
- "name": f"Does {title} require root access?",
135
- "acceptedAnswer": {
136
- "@type": "Answer",
137
- "text": "No, you do not need root access to install our modification. It works on any non-rooted Android device."
138
- }
139
- }
140
  ]
141
  }
142
-
143
  schema_sysreq = {
144
- "@context": "https://schema.org",
145
- "@type": "SoftwareApplication",
146
- "name": title,
147
- "operatingSystem": "ANDROID",
148
- "applicationCategory": category,
149
- "author": {
150
- "@type": "Organization",
151
- "name": developer
152
- },
153
- "requirements": {
154
- "@type": "SoftwareApplicationRequirements",
155
- "name": "Minimum System Requirements",
156
- "operatingSystem": "Android 9.0 (Pie)+",
157
- "ram": "4 GB+",
158
- "storage": "100 MB+"
159
- }
160
  }
161
-
162
  schema_script = f'<script type="application/ld+json">\n[{json.dumps(schema_app)}, {json.dumps(schema_faq)}, {json.dumps(schema_sysreq)}]\n</script>'
163
 
164
- # 6. Read and "Apply" the Theme from the Dataset
165
  try:
166
- downloaded_theme_path = hf_hub_download(
167
- repo_id=DATASET_REPO,
168
- filename=THEME_FILE_NAME,
169
- repo_type="dataset",
170
- token=HF_TOKEN
171
- )
172
- # Since we cannot parse the XML in this environment, we will simulate
173
- # extracting a primary theme color from the XML file. We will assume
174
- # the user uses the `theme_color` picker from the UI as a way to specify
175
- # this primary color in their website's theme.
176
- theme_background = "#0f172a" # Default
177
- # extracted_theme_color = theme_color # Simulated
178
-
179
  except Exception:
180
- # If no theme is found, use defaults
181
  theme_background = "#0f172a"
182
- # extracted_theme_color = "#3b82f6"
183
 
184
- # 7. Build the Final HTML Template, integrated with deep shadows from example
185
  html_template = f"""
186
  {schema_script}
 
 
 
 
 
 
187
  <div style="display: none;">
188
  <img src="{icon_url}" alt="Download {title} Mod APK" />
189
  </div>
@@ -204,7 +165,7 @@ def generate_blogger_html(app_query, custom_link, mod_features, theme_color, tel
204
  </div>
205
 
206
  <div style="text-align: center; margin-bottom: 35px; background: #020617; padding: 25px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.02);">
207
- <a href="{custom_link}" target="_blank" rel="nofollow noopener" style="display: inline-block; width: 100%; max-width: 400px; background: {theme_color}; color: #fff; text-decoration: none; padding: 16px; border-radius: 50px; font-weight: 800; font-size: 1.15rem; box-shadow: 0 0 25px {theme_color}80; transition: transform 0.3s ease;">
208
  <i class="fa-solid fa-cloud-arrow-down" style="margin-right: 8px;"></i> Download APK via Secure Link
209
  </a>
210
  <p style="font-size: 0.8rem; color: #64748b; margin-top: 15px; margin-bottom: 0;">
@@ -214,7 +175,7 @@ def generate_blogger_html(app_query, custom_link, mod_features, theme_color, tel
214
 
215
  <div style="background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.2); padding: 15px; border-radius: 12px; text-align: center; margin-bottom: 30px;">
216
  <strong style="color: #e2e8f0; margin-right: 15px;">Join Lexical Space Community:</strong>
217
- <a href="{telegram_link}" target="_blank" style="display: inline-block; background: #0088cc; color: white; padding: 8px 20px; border-radius: 20px; text-decoration: none; font-weight: bold; font-size: 0.9rem;">✈️ Telegram</a>
218
  </div>
219
 
220
  <h2 style="border-left: 4px solid #38bdf8; padding-left: 12px; margin-bottom: 20px; font-size: 1.3rem;">App Information</h2>
@@ -225,6 +186,9 @@ def generate_blogger_html(app_query, custom_link, mod_features, theme_color, tel
225
  <div><span style="display: block; font-size: 0.8rem; color: #94a3b8; text-transform: uppercase;">File Size</span><strong>{final_size}</strong></div>
226
  </div>
227
 
 
 
 
228
  <h2 style="border-left: 4px solid {theme_color}; padding-left: 12px; margin-bottom: 15px; font-size: 1.3rem;">Max Store Modifications</h2>
229
  <div style="background: rgba(16, 185, 129, 0.05); border: 1px solid rgba(16, 185, 129, 0.2); padding: 20px; border-radius: 12px; margin-bottom: 35px; box-shadow: 0 5px 15px rgba(16, 185, 129, 0.1);">
230
  <ul style="color: #f1f5f9; line-height: 1.8; list-style-type: none; padding-left: 0; margin: 0; font-weight: 600;">
@@ -242,31 +206,83 @@ def generate_blogger_html(app_query, custom_link, mod_features, theme_color, tel
242
  </div>
243
  </div>
244
  """
245
- # Calculate Stats
246
  word_count = str(len(html_template.split()))
247
- keyword_density = str(html_template.lower().count(title.lower()))
248
 
249
- # Save Draft locally for backup download
250
  with open("latest_draft.txt", "w", encoding="utf-8") as f:
251
  f.write(html_template)
252
 
253
  return html_template, html_template, word_count, keyword_density
254
 
255
  except Exception as e:
256
- return f"Error generating HTML: {str(e)}", "<b>Error occurred</b>", "0", "0"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
257
 
258
  # -----------------------------
259
  # UI & AUTHENTICATION LOGIC
260
  # -----------------------------
261
- # Gradio 6.0 fix: Remove theme from constructor
262
- with gr.Blocks() as agent_ui:
263
- # State variable to track authentication session
264
  session_state = gr.State({"logged_in": False, "login_time": 0})
265
 
266
  # --- LOGIN SCREEN ---
267
  with gr.Group(visible=True) as login_screen:
268
  gr.Markdown("# πŸ”’ Lexical Space Internal Tool")
269
- gr.Markdown("Please enter the password to unlock the Post Generator Agent.")
270
 
271
  with gr.Row():
272
  pwd_input = gr.Textbox(label="Password", type="password", scale=4)
@@ -274,43 +290,54 @@ with gr.Blocks() as agent_ui:
274
 
275
  login_err = gr.Markdown("❌ Incorrect password.", visible=False)
276
 
277
- # --- MAIN APPLICATION ---
278
  with gr.Group(visible=False) as main_app:
279
  gr.Markdown("# πŸš€ Lexical Space V2 Post Generator")
280
  gr.Markdown("Search the Play Store, inject mods, and generate SEO-optimized HTML for your Blogger post.")
281
 
282
  with gr.Row():
 
283
  with gr.Column(scale=1):
284
  gr.Markdown("### 1. App Parameters")
285
- app_input = gr.Textbox(label="App Name (e.g., TeraBox)", placeholder="Search query for Play Store...")
286
  link_input = gr.Textbox(label="Your Download Link (Mega, PixelDrain, etc.)", placeholder="https://...")
287
- features_input = gr.Textbox(label="Mod Features (Comma separated)", placeholder="Premium Unlocked, No Ads", value="Premium Unlocked, No Ads")
288
 
289
- # New Theme & XML Upload Accordion
290
  with gr.Accordion("Theme & XML Upload (Optional)", open=False):
291
  xml_theme_input = gr.File(label="Upload Website Theme XML")
292
  save_theme_btn = gr.Button("Save Theme to Dataset")
293
  theme_status_out = gr.Textbox(label="Theme Status", interactive=False)
294
 
295
- # Advanced SEO & Theme Settings Accordion
296
  with gr.Accordion("Advanced SEO & Theme Settings", open=False):
297
  theme_color = gr.ColorPicker(label="Post Accent Color (Applied from Theme)", value="#3b82f6")
298
- telegram_link = gr.Textbox(label="Your Telegram Channel Link", value="https://t.me/lexicalspace")
299
  custom_size = gr.Textbox(label="Custom File Size Override (e.g. 150 MB)", placeholder="Optional Override...")
300
  custom_date = gr.Textbox(label="Custom Updated Date Override (e.g. January 15, 2026)", placeholder="Optional Override...")
301
 
302
- generate_btn = gr.Button("βš™οΈ Generate HTML", variant="primary")
303
 
 
 
304
  with gr.Row():
305
- word_count_out = gr.Textbox(label="Total Word Count", interactive=False)
306
- kw_density_out = gr.Textbox(label="Keyword Density", interactive=False)
 
 
 
 
 
307
 
308
- # Crash Fix: Remove the initial 'latest_draft.txt' value
 
 
 
 
309
 
310
  file_download = gr.File(label="Download Draft Backup (.txt file)", visible=False, interactive=False)
311
 
 
312
  with gr.Column(scale=2):
313
- gr.Markdown("### 2. Output & Preview")
314
  with gr.Tabs():
315
  with gr.Tab("Source Code (HTML)"):
316
  html_output = gr.Code(label="Copy & Paste to Blogger HTML View", language="html", interactive=False)
@@ -319,9 +346,8 @@ with gr.Blocks() as agent_ui:
319
 
320
  # --- CONTROLLERS ---
321
  def verify_login(pwd, state):
322
- """Checks the password against the hf secret and updates session state."""
323
  if not APP_PASSWORD:
324
- return state, gr.update(), gr.update(), gr.update(value="❌ APP_PASSWORD missing from secrets.", visible=True)
325
 
326
  if pwd == APP_PASSWORD:
327
  state["logged_in"] = True
@@ -331,60 +357,48 @@ with gr.Blocks() as agent_ui:
331
  return state, gr.update(visible=True), gr.update(visible=False), gr.update(value="❌ Incorrect password.", visible=True)
332
 
333
  def secure_generate(app_query, custom_link, mod_features, t_color, t_link, c_size, c_date, state):
334
- """Validates the 30-minute session before running the generation."""
335
- # 1800 seconds = 30 minutes
336
  if not state.get("logged_in") or (time.time() - state.get("login_time", 0) > 1800):
337
  state["logged_in"] = False
338
- return (
339
- "Session expired. Please refresh and log in again.", # html_output
340
- "", # preview_output
341
- "0", # word_count
342
- "0", # kw_density
343
- gr.update(visible=True), # login_screen
344
- gr.update(visible=False), # main_app
345
- state, # session_state
346
- gr.update(visible=False) # file download
347
- )
348
 
349
- # Run generation if session is valid
350
  code, preview, words, keywords = generate_blogger_html(app_query, custom_link, mod_features, t_color, t_link, c_size, c_date)
351
 
352
- # --- Crash Fix: Only show the download button if generation succeeded and file exists ---
353
  if os.path.exists("latest_draft.txt") and not code.startswith("Error"):
354
  file_update = gr.update(value="latest_draft.txt", visible=True)
355
  else:
356
  file_update = gr.update(value=None, visible=False)
357
 
358
- return (
359
- code, preview, words, keywords,
360
- gr.update(visible=False), # Keep login screen hidden
361
- gr.update(visible=True), # Keep main app visible
362
- state,
363
- file_update
364
- )
365
 
366
  # --- EVENT BINDINGS ---
367
  login_btn.click(
368
- fn=verify_login,
369
- inputs=[pwd_input, session_state],
370
  outputs=[session_state, login_screen, main_app, login_err]
371
  )
372
-
373
  save_theme_btn.click(
374
- fn=save_theme_to_dataset,
375
- inputs=[xml_theme_input],
376
  outputs=theme_status_out
377
  )
378
 
379
- # Target the file download component inside the main_app tree
380
-
381
-
382
  generate_btn.click(
383
  fn=secure_generate,
384
  inputs=[app_input, link_input, features_input, theme_color, telegram_link, custom_size, custom_date, session_state],
385
  outputs=[html_output, preview_output, word_count_out, kw_density_out, login_screen, main_app, session_state, file_download]
386
  )
 
 
 
 
 
 
 
 
387
 
388
  if __name__ == "__main__":
389
- # Gradio 6.0 fix: Move theme into launch()
390
- agent_ui.launch(theme=gr.themes.Monochrome(primary_hue="blue"))
 
1
  import gradio as gr
2
+ from google_play_scraper import search, app as play_app
3
  import os
4
  import time
5
  from datetime import datetime
6
  import json
7
+ import google.generativeai as genai
8
 
9
  from huggingface_hub import HfApi, hf_hub_download, create_repo
10
 
11
  # -----------------------------
12
+ # CONFIG & HF/GEMINI CONNECTION
13
  # -----------------------------
14
  HF_TOKEN = os.getenv("HF_TOKEN")
15
+ APP_PASSWORD = os.getenv("APP_PASSWORD")
16
+ GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
17
  DATASET_REPO = "kacapower/lexical-space-data"
18
  THEME_FILE_NAME = "website_theme.xml"
19
 
20
+ # -----------------------------
21
+ # CORE FUNCTIONS
22
+ # -----------------------------
23
  def save_theme_to_dataset(xml_file):
24
  """Saves the uploaded XML file directly to the private hf dataset."""
25
  if xml_file is None:
26
  return "❌ Please upload an XML file first."
27
 
28
  try:
 
29
  file_path = xml_file if isinstance(xml_file, str) else xml_file.name
30
 
 
31
  try:
32
  create_repo(
33
  repo_id=DATASET_REPO,
 
39
  except Exception as repo_err:
40
  return f"❌ Failed to access or create repository: {repo_err}. Check your HF_TOKEN permissions."
41
 
 
42
  api = HfApi()
43
  api.upload_file(
44
  path_or_fileobj=file_path,
 
53
  except Exception as e:
54
  return f"❌ Error saving theme to dataset: {e}"
55
 
56
+
57
  def generate_blogger_html(app_query, custom_link, mod_features, theme_color, telegram_link, custom_size, custom_date):
58
+ """Scrapes Google Play and generates the base SEO-optimized Blogger HTML."""
59
  try:
 
60
  search_results = search(app_query, lang='en', country='us')
61
  if not search_results:
62
+ return "Error: Could not find this app on the Play Store.", "<b>App not found</b>", "0", "0"
63
 
 
64
  app_id = search_results[0]['appId']
65
+ app_details = play_app(app_id, lang='en', country='us')
66
 
67
+ # Data Extraction
68
  title = app_details.get('title', app_query)
69
  icon_url = app_details.get('icon', '')
70
  rating = round(app_details.get('score', 4.5), 1)
 
74
  installs = app_details.get('installs', '10,000+')
75
  version = app_details.get('version', 'Varies with device')
76
 
 
77
  scraped_size = app_details.get('size', 'Varies')
78
+ final_size = custom_size.strip() if custom_size else scraped_size
79
 
 
80
  updated_ts = app_details.get('updated', 0)
81
  scraped_date = datetime.fromtimestamp(updated_ts).strftime('%B %d, %Y') if updated_ts else "Recently"
82
+ updated_date = custom_date.strip() if custom_date else scraped_date
83
 
 
84
  description = app_details.get('summary', 'Premium application with enhanced features.')
85
  full_description = app_details.get('descriptionHTML', description)
86
 
87
+ # Media Extraction (Enhanced responsiveness)
88
+ screenshots = app_details.get('screenshots', [])[:4] # Take up to 4 screenshots
89
+ video_url = app_details.get('video', None)
90
+
91
+ # Mod Features Formatting
92
  features_list = ""
93
  for feature in mod_features.split(','):
94
  if feature.strip():
95
  features_list += f'<li style="margin-bottom: 8px;"><i class="fa-solid fa-check" style="color: {theme_color}; margin-right: 10px;"></i> {feature.strip()}</li>\n'
96
 
97
+ # Screenshot HTML Generation
98
+ screenshots_html = ""
99
+ if screenshots:
100
+ screenshots_html = f'<h2 style="border-left: 4px solid {theme_color}; padding-left: 12px; margin-top: 30px;">App Screenshots</h2><div style="display: flex; gap: 15px; overflow-x: auto; padding-bottom: 15px; scroll-snap-type: x mandatory;">'
101
+ for snap in screenshots:
102
+ screenshots_html += f'<img src="{snap}" style="height: 280px; border-radius: 12px; border: 1px solid rgba(255,255,255,0.1); scroll-snap-align: start; box-shadow: 0 4px 10px rgba(0,0,0,0.3);">'
103
+ screenshots_html += '</div>'
104
+
105
+ # Video HTML Generation
106
+ video_html = ""
107
+ if video_url:
108
+ embed_url = video_url.replace("watch?v=", "embed/")
109
+ video_html = f'<h2 style="border-left: 4px solid {theme_color}; padding-left: 12px; margin-top: 30px;">Official Trailer</h2><div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; border-radius: 16px; box-shadow: 0 5px 15px rgba(0,0,0,0.4);"><iframe src="{embed_url}" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" frameborder="0" allowfullscreen></iframe></div>'
110
+
111
+ # SEO Schemas (App, FAQ, System Requirements)
112
  schema_app = {
113
+ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": title, "operatingSystem": "ANDROID", "applicationCategory": category, "image": icon_url,
114
+ "author": { "@type": "Organization", "name": developer },
115
+ "aggregateRating": { "@type": "AggregateRating", "ratingValue": str(rating), "ratingCount": str(reviews_count) },
116
+ "offers": { "@type": "Offer", "price": "0.00", "priceCurrency": "USD" }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
 
 
118
  schema_faq = {
119
+ "@context": "https://schema.org", "@type": "FAQPage",
 
120
  "mainEntity": [
121
+ {"@type": "Question", "name": f"Is {title} Mod APK safe?", "acceptedAnswer": {"@type": "Answer", "text": "Yes, our mods are thoroughly scanned and tested."}},
122
+ {"@type": "Question", "name": f"Does {title} require root access?", "acceptedAnswer": {"@type": "Answer", "text": "No, it works on any non-rooted Android device."}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  ]
124
  }
 
125
  schema_sysreq = {
126
+ "@context": "https://schema.org", "@type": "SoftwareApplication", "name": title, "operatingSystem": "ANDROID", "applicationCategory": category,
127
+ "author": { "@type": "Organization", "name": developer },
128
+ "requirements": { "@type": "SoftwareApplicationRequirements", "name": "Minimum System Requirements", "operatingSystem": "Android 9.0 (Pie)+", "ram": "4 GB+", "storage": "100 MB+" }
 
 
 
 
 
 
 
 
 
 
 
 
 
129
  }
 
130
  schema_script = f'<script type="application/ld+json">\n[{json.dumps(schema_app)}, {json.dumps(schema_faq)}, {json.dumps(schema_sysreq)}]\n</script>'
131
 
132
+ # Fetch Theme XML if available
133
  try:
134
+ downloaded_theme_path = hf_hub_download(repo_id=DATASET_REPO, filename=THEME_FILE_NAME, repo_type="dataset", token=HF_TOKEN)
135
+ theme_background = "#0f172a"
 
 
 
 
 
 
 
 
 
 
 
136
  except Exception:
 
137
  theme_background = "#0f172a"
 
138
 
139
+ # Final Base HTML Template Construction
140
  html_template = f"""
141
  {schema_script}
142
+ <style>
143
+ @keyframes pulse {{ 0% {{ transform: scale(1); }} 50% {{ transform: scale(1.02); box-shadow: 0 0 30px {theme_color}80; }} 100% {{ transform: scale(1); }} }}
144
+ .lex-btn:hover {{ filter: brightness(1.2); }}
145
+ .app-post-wrapper * {{ box-sizing: border-box; }}
146
+ </style>
147
+
148
  <div style="display: none;">
149
  <img src="{icon_url}" alt="Download {title} Mod APK" />
150
  </div>
 
165
  </div>
166
 
167
  <div style="text-align: center; margin-bottom: 35px; background: #020617; padding: 25px; border-radius: 16px; border: 1px solid rgba(255,255,255,0.02);">
168
+ <a href="{custom_link}" target="_blank" rel="nofollow noopener" class="lex-btn" style="display: inline-block; width: 100%; max-width: 400px; background: {theme_color}; color: #fff; text-decoration: none; padding: 16px; border-radius: 50px; font-weight: 800; font-size: 1.15rem; box-shadow: 0 0 25px {theme_color}80; transition: transform 0.3s ease; animation: pulse 2s infinite;">
169
  <i class="fa-solid fa-cloud-arrow-down" style="margin-right: 8px;"></i> Download APK via Secure Link
170
  </a>
171
  <p style="font-size: 0.8rem; color: #64748b; margin-top: 15px; margin-bottom: 0;">
 
175
 
176
  <div style="background: rgba(56, 189, 248, 0.1); border: 1px solid rgba(56, 189, 248, 0.2); padding: 15px; border-radius: 12px; text-align: center; margin-bottom: 30px;">
177
  <strong style="color: #e2e8f0; margin-right: 15px;">Join Lexical Space Community:</strong>
178
+ <a href="{telegram_link}" target="_blank" style="display: inline-block; background: #0088cc; color: white; padding: 8px 20px; border-radius: 20px; text-decoration: none; font-weight: bold; font-size: 0.9rem; transition: 0.2s;">✈️ Telegram</a>
179
  </div>
180
 
181
  <h2 style="border-left: 4px solid #38bdf8; padding-left: 12px; margin-bottom: 20px; font-size: 1.3rem;">App Information</h2>
 
186
  <div><span style="display: block; font-size: 0.8rem; color: #94a3b8; text-transform: uppercase;">File Size</span><strong>{final_size}</strong></div>
187
  </div>
188
 
189
+ {video_html}
190
+ {screenshots_html}
191
+
192
  <h2 style="border-left: 4px solid {theme_color}; padding-left: 12px; margin-bottom: 15px; font-size: 1.3rem;">Max Store Modifications</h2>
193
  <div style="background: rgba(16, 185, 129, 0.05); border: 1px solid rgba(16, 185, 129, 0.2); padding: 20px; border-radius: 12px; margin-bottom: 35px; box-shadow: 0 5px 15px rgba(16, 185, 129, 0.1);">
194
  <ul style="color: #f1f5f9; line-height: 1.8; list-style-type: none; padding-left: 0; margin: 0; font-weight: 600;">
 
206
  </div>
207
  </div>
208
  """
 
209
  word_count = str(len(html_template.split()))
210
+ keyword_density = str(html_template.lower().count(title.lower()[:10])) # Safer substring counting
211
 
 
212
  with open("latest_draft.txt", "w", encoding="utf-8") as f:
213
  f.write(html_template)
214
 
215
  return html_template, html_template, word_count, keyword_density
216
 
217
  except Exception as e:
218
+ return f"Error generating HTML: {str(e)}", f"<b>Error occurred: {str(e)}</b>", "0", "0"
219
+
220
+ # -----------------------------
221
+ # GEMINI AI ENHANCER
222
+ # -----------------------------
223
+ def enhance_with_gemini(raw_html, style_name, state):
224
+ """Uses Google Gemini to restructure and theme the HTML while retaining functionality."""
225
+ if not state.get("logged_in") or (time.time() - state.get("login_time", 0) > 1800):
226
+ return "Session expired.", "Session Expired.", "0", "0", gr.update(visible=False)
227
+
228
+ if not GEMINI_API_KEY:
229
+ return raw_html, "<b>Error: GEMINI_API_KEY not found in HF secrets.</b>", "0", "0", gr.update(visible=False)
230
+
231
+ if not raw_html or raw_html.startswith("Error"):
232
+ return raw_html, "<b>Please generate Base HTML first before enhancing.</b>", "0", "0", gr.update(visible=False)
233
+
234
+ try:
235
+ genai.configure(api_key=GEMINI_API_KEY)
236
+ model = genai.GenerativeModel('gemini-1.5-flash')
237
+
238
+ prompt = f"""
239
+ You are the lead front-end developer for the 'Lexical Space' blog.
240
+ I have generated the following raw HTML for an app post. It contains accurate factual data, JSON-LD schemas, exact image links, download URLs, and video iframes.
241
+ Your task is to enhance and re-style this HTML to perfectly match the '{style_name}' theme.
242
+
243
+ STRICT RULES:
244
+ 1. CRITICAL: DO NOT remove or alter the `<script type="application/ld+json">` blocks. They are critical for SEO.
245
+ 2. CRITICAL: DO NOT alter ANY `href="..."` or `src="..."` attributes. The download links, Telegram links, image links, and video iframes MUST remain exactly as they are.
246
+ 3. DO NOT hallucinate or change the factual data (file size, version, rating, text content).
247
+ 4. ONLY output raw HTML. Do not wrap your response in ```html blocks. Do not add conversational intro/outro text.
248
+
249
+ Theme Guidelines for '{style_name}':
250
+ - If 'Lexical Standard': Deep space background (#0f172a), neon accents, glassmorphism filters, glowing borders, and pulse animations.
251
+ - If 'Lexical Minimalist': Cleaner, fewer borders, flat white/light-gray or pure black backgrounds, high readability, subtle drop shadows instead of neon glow.
252
+ - If 'Lexical Max Store': Replicate a premium mobile app store UI (like Google Play / App Store). Use grid layouts, highly rounded corners (24px), large action buttons, and emphasize the app icon and download statistics.
253
+
254
+ Base HTML Code to Enhance:
255
+ {raw_html}
256
+ """
257
+
258
+ response = model.generate_content(prompt)
259
+ enhanced_html = response.text.replace("```html", "").replace("```", "").strip()
260
+
261
+ # Calculate new stats
262
+ word_count = str(len(enhanced_html.split()))
263
+ keyword_density = "N/A (AI Enhanced)"
264
+
265
+ # Overwrite draft with enhanced version
266
+ with open("latest_draft.txt", "w", encoding="utf-8") as f:
267
+ f.write(enhanced_html)
268
+
269
+ return enhanced_html, enhanced_html, word_count, keyword_density, gr.update(value="latest_draft.txt", visible=True)
270
+
271
+ except Exception as e:
272
+ return raw_html, f"<b>Gemini API Error:</b> {str(e)}<br><i>Showing unenhanced Base HTML instead.</i>", "0", "0", gr.update(visible=False)
273
+
274
 
275
  # -----------------------------
276
  # UI & AUTHENTICATION LOGIC
277
  # -----------------------------
278
+ with gr.Blocks(title="Lexical Space Internal Tool", theme=gr.themes.Monochrome(primary_hue="blue")) as agent_ui:
279
+ # State tracking
 
280
  session_state = gr.State({"logged_in": False, "login_time": 0})
281
 
282
  # --- LOGIN SCREEN ---
283
  with gr.Group(visible=True) as login_screen:
284
  gr.Markdown("# πŸ”’ Lexical Space Internal Tool")
285
+ gr.Markdown("Please enter the password to unlock the Post Generator Agent. Sessions expire after 30 minutes.")
286
 
287
  with gr.Row():
288
  pwd_input = gr.Textbox(label="Password", type="password", scale=4)
 
290
 
291
  login_err = gr.Markdown("❌ Incorrect password.", visible=False)
292
 
293
+ # --- MAIN APPLICATION DASHBOARD ---
294
  with gr.Group(visible=False) as main_app:
295
  gr.Markdown("# πŸš€ Lexical Space V2 Post Generator")
296
  gr.Markdown("Search the Play Store, inject mods, and generate SEO-optimized HTML for your Blogger post.")
297
 
298
  with gr.Row():
299
+ # --- LEFT COLUMN: SETTINGS ---
300
  with gr.Column(scale=1):
301
  gr.Markdown("### 1. App Parameters")
302
+ app_input = gr.Textbox(label="App Name (e.g., TeraBox, YouTube ReVanced)", placeholder="Search query for Play Store...")
303
  link_input = gr.Textbox(label="Your Download Link (Mega, PixelDrain, etc.)", placeholder="https://...")
304
+ features_input = gr.Textbox(label="Mod Features (Comma separated)", placeholder="Premium Unlocked, No Ads", value="Premium Unlocked, No Ads, Analytics Removed")
305
 
 
306
  with gr.Accordion("Theme & XML Upload (Optional)", open=False):
307
  xml_theme_input = gr.File(label="Upload Website Theme XML")
308
  save_theme_btn = gr.Button("Save Theme to Dataset")
309
  theme_status_out = gr.Textbox(label="Theme Status", interactive=False)
310
 
 
311
  with gr.Accordion("Advanced SEO & Theme Settings", open=False):
312
  theme_color = gr.ColorPicker(label="Post Accent Color (Applied from Theme)", value="#3b82f6")
313
+ telegram_link = gr.Textbox(label="Your Telegram Channel Link", value="https://t.me/lexicalspace") # Fixed raw URL mapping
314
  custom_size = gr.Textbox(label="Custom File Size Override (e.g. 150 MB)", placeholder="Optional Override...")
315
  custom_date = gr.Textbox(label="Custom Updated Date Override (e.g. January 15, 2026)", placeholder="Optional Override...")
316
 
317
+ generate_btn = gr.Button("βš™οΈ Generate Base HTML", variant="primary")
318
 
319
+ gr.Markdown("<hr>")
320
+ gr.Markdown("### 2. AI Style Enhancer (Gemini)")
321
  with gr.Row():
322
+ style_dropdown = gr.Dropdown(
323
+ choices=["Lexical Standard", "Lexical Minimalist", "Lexical Max Store"],
324
+ label="Target Post Style",
325
+ value="Lexical Standard",
326
+ scale=2
327
+ )
328
+ enhance_btn = gr.Button("✨ Enhance Code", variant="secondary", scale=1)
329
 
330
+ gr.Markdown("<hr>")
331
+ gr.Markdown("### Post Statistics")
332
+ with gr.Row():
333
+ word_count_out = gr.Textbox(label="Total Word Count", interactive=False)
334
+ kw_density_out = gr.Textbox(label="Keyword Mentions", interactive=False)
335
 
336
  file_download = gr.File(label="Download Draft Backup (.txt file)", visible=False, interactive=False)
337
 
338
+ # --- RIGHT COLUMN: OUTPUT ---
339
  with gr.Column(scale=2):
340
+ gr.Markdown("### 3. Output & Preview")
341
  with gr.Tabs():
342
  with gr.Tab("Source Code (HTML)"):
343
  html_output = gr.Code(label="Copy & Paste to Blogger HTML View", language="html", interactive=False)
 
346
 
347
  # --- CONTROLLERS ---
348
  def verify_login(pwd, state):
 
349
  if not APP_PASSWORD:
350
+ return state, gr.update(), gr.update(), gr.update(value="❌ APP_PASSWORD missing from Space secrets.", visible=True)
351
 
352
  if pwd == APP_PASSWORD:
353
  state["logged_in"] = True
 
357
  return state, gr.update(visible=True), gr.update(visible=False), gr.update(value="❌ Incorrect password.", visible=True)
358
 
359
  def secure_generate(app_query, custom_link, mod_features, t_color, t_link, c_size, c_date, state):
360
+ # Validate session
 
361
  if not state.get("logged_in") or (time.time() - state.get("login_time", 0) > 1800):
362
  state["logged_in"] = False
363
+ return ("Session expired.", "Session expired.", "0", "0", gr.update(visible=True), gr.update(visible=False), state, gr.update(visible=False))
 
 
 
 
 
 
 
 
 
364
 
365
+ # Generate HTML
366
  code, preview, words, keywords = generate_blogger_html(app_query, custom_link, mod_features, t_color, t_link, c_size, c_date)
367
 
368
+ # Enable file download if successful
369
  if os.path.exists("latest_draft.txt") and not code.startswith("Error"):
370
  file_update = gr.update(value="latest_draft.txt", visible=True)
371
  else:
372
  file_update = gr.update(value=None, visible=False)
373
 
374
+ return (code, preview, words, keywords, gr.update(visible=False), gr.update(visible=True), state, file_update)
 
 
 
 
 
 
375
 
376
  # --- EVENT BINDINGS ---
377
  login_btn.click(
378
+ fn=verify_login,
379
+ inputs=[pwd_input, session_state],
380
  outputs=[session_state, login_screen, main_app, login_err]
381
  )
382
+
383
  save_theme_btn.click(
384
+ fn=save_theme_to_dataset,
385
+ inputs=[xml_theme_input],
386
  outputs=theme_status_out
387
  )
388
 
 
 
 
389
  generate_btn.click(
390
  fn=secure_generate,
391
  inputs=[app_input, link_input, features_input, theme_color, telegram_link, custom_size, custom_date, session_state],
392
  outputs=[html_output, preview_output, word_count_out, kw_density_out, login_screen, main_app, session_state, file_download]
393
  )
394
+
395
+ # Show loading icon specifically on the AI enhance button
396
+ enhance_btn.click(
397
+ fn=enhance_with_gemini,
398
+ inputs=[html_output, style_dropdown, session_state],
399
+ outputs=[html_output, preview_output, word_count_out, kw_density_out, file_download],
400
+ show_progress="full"
401
+ )
402
 
403
  if __name__ == "__main__":
404
+ agent_ui.launch()