AllanHill commited on
Commit
06224db
·
verified ·
1 Parent(s): 18b105b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +767 -0
app.py ADDED
@@ -0,0 +1,767 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import re
3
+ import json
4
+ from datetime import datetime
5
+ from PIL import Image
6
+ import io
7
+ import os
8
+ from dotenv import load_dotenv
9
+ import gradio as gr
10
+ import base64
11
+ from io import BytesIO
12
+
13
+ # ========== LOAD ENVIRONMENT VARIABLES ==========
14
+ def load_environment_variables():
15
+ try:
16
+ load_dotenv(dotenv_path='rank_cd.env')
17
+ return {
18
+ 'url': os.getenv('WORDPRESS_URL', 'https://cdgarment.com'),
19
+ 'username': os.getenv('WORDPRESS_USERNAME', ''),
20
+ 'password': os.getenv('WORDPRESS_APP_PASSWORD', ''),
21
+ 'status': os.getenv('DEFAULT_STATUS', 'draft')
22
+ }
23
+ except Exception as e:
24
+ print(f"Could not load rank_cd.env: {str(e)}")
25
+ return {
26
+ 'url': 'https://cdgarment.com',
27
+ 'username': '',
28
+ 'password': '',
29
+ 'status': 'draft'
30
+ }
31
+
32
+ # ========== FUNCTIONS ==========
33
+ def parse_gemini_content(content, post_status='draft'):
34
+ """Parse Gemini content for cdgarment.com with Rank Math"""
35
+ data = {
36
+ 'seo_title': '',
37
+ 'primary_keyword': '',
38
+ 'meta_description': '',
39
+ 'tags': [],
40
+ 'article_title': '',
41
+ 'content': '',
42
+ 'url_slug': '',
43
+ 'rank_math_meta': {}
44
+ }
45
+
46
+ try:
47
+ # Extract SEO Title
48
+ seo_title_match = re.search(r'SEO Title:\s*(.+?)(?=\n)', content)
49
+ if seo_title_match:
50
+ data['seo_title'] = seo_title_match.group(1).strip()
51
+
52
+ # Extract Primary Keyword
53
+ keyword_match = re.search(r'Primary Keyword:\s*(.+?)(?=\n)', content)
54
+ if keyword_match:
55
+ data['primary_keyword'] = keyword_match.group(1).strip()
56
+
57
+ # Extract Meta Description
58
+ meta_match = re.search(r'Meta Description:\s*(.+?)(?=\n)', content)
59
+ if meta_match:
60
+ data['meta_description'] = meta_match.group(1).strip()
61
+
62
+ # Extract Tags
63
+ tags_match = re.search(r'Tags:\s*(.+?)(?=\n\|📝)', content)
64
+ if tags_match:
65
+ tags_str = tags_match.group(1).strip()
66
+ data['tags'] = [tag.strip() for tag in re.split(r'[,;]', tags_str) if tag.strip()]
67
+
68
+ # Extract Article Content
69
+ article_match = re.search(r'(?:📝\|▶\|●\|◆).*?(?:Article\|Content)[:\-]?\s*(.+)', content, re.DOTALL)
70
+ if article_match:
71
+ full_content = article_match.group(1).strip()
72
+ lines = full_content.split('\n')
73
+ if lines:
74
+ data['article_title'] = lines[0].strip()
75
+ data['content'] = '\n'.join(lines[1:])
76
+
77
+ # Generate URL slug
78
+ if data['seo_title']:
79
+ slug = data['seo_title'].lower()
80
+ slug = re.sub(r'[^\w\s-]', '', slug)
81
+ slug = re.sub(r'[-\s]+', '-', slug)
82
+ data['url_slug'] = slug[:100]
83
+
84
+ # Fallback title
85
+ if not data['seo_title'] and data['article_title']:
86
+ data['seo_title'] = data['article_title']
87
+
88
+ # Prepare Rank Math meta fields (based on your screenshot)
89
+ data['rank_math_meta'] = {
90
+ 'rank_math_title': data['seo_title'],
91
+ 'rank_math_description': data['meta_description'],
92
+ 'rank_math_robots': ['index'] if post_status == 'publish' else ['noindex'],
93
+ 'rank_math_news_sitemap_robots': 'index',
94
+ 'rank_math_facebook_title': data['seo_title'],
95
+ 'rank_math_facebook_description': data['meta_description'],
96
+ 'rank_math_twitter_title': data['seo_title'],
97
+ 'rank_math_twitter_description': data['meta_description'],
98
+ 'rank_math_canonical_url': '',
99
+ }
100
+
101
+ # If we have primary keyword, add it (though not in your screenshot)
102
+ if data['primary_keyword']:
103
+ data['rank_math_meta']['rank_math_focus_keyword'] = data['primary_keyword']
104
+
105
+ return data
106
+
107
+ except Exception as e:
108
+ return {'error': f"Error parsing: {str(e)}"}
109
+
110
+ def upload_image_to_wordpress(image_file, wp_config, filename_slug):
111
+ """Upload image to WordPress with auto-naming"""
112
+ try:
113
+ # Handle Gradio image input (can be PIL Image or path)
114
+ if hasattr(image_file, 'name'): # It's a file object
115
+ file_extension = image_file.name.split('.')[-1].lower()
116
+ image_data = image_file.read()
117
+ elif isinstance(image_file, str): # It's a file path
118
+ file_extension = image_file.split('.')[-1].lower()
119
+ with open(image_file, 'rb') as f:
120
+ image_data = f.read()
121
+ else: # Assume it's already bytes
122
+ image_data = image_file
123
+
124
+ # Generate filename from slug
125
+ filename = f"{filename_slug}.{file_extension}"
126
+
127
+ # Prepare image data
128
+ files = {
129
+ 'file': (filename, image_data, f'image/{file_extension}')
130
+ }
131
+
132
+ auth = (wp_config['username'], wp_config['password'])
133
+
134
+ # Upload to WordPress
135
+ response = requests.post(
136
+ f"{wp_config['url']}/wp-json/wp/v2/media",
137
+ auth=auth,
138
+ files=files
139
+ )
140
+
141
+ if response.status_code == 201:
142
+ media_data = response.json()
143
+
144
+ # Update alt text with SEO title
145
+ update_response = requests.post(
146
+ f"{wp_config['url']}/wp-json/wp/v2/media/{media_data['id']}",
147
+ auth=auth,
148
+ json={
149
+ 'alt_text': filename_slug.replace('-', ' ').title(),
150
+ 'caption': filename_slug.replace('-', ' ').title(),
151
+ 'description': f"Featured image for: {filename_slug.replace('-', ' ').title()}"
152
+ }
153
+ )
154
+
155
+ return media_data['id']
156
+ else:
157
+ return None
158
+
159
+ except Exception as e:
160
+ print(f"Error uploading image: {str(e)}")
161
+ return None
162
+
163
+ def create_wordpress_post(parsed_data, wp_config, media_id=None, post_status='draft'):
164
+ """Create WordPress post with Rank Math meta"""
165
+ try:
166
+ # Base post data
167
+ post_data = {
168
+ 'title': parsed_data['seo_title'],
169
+ 'content': parsed_data['content'],
170
+ 'slug': parsed_data['url_slug'],
171
+ 'status': post_status,
172
+ 'meta': parsed_data['rank_math_meta']
173
+ }
174
+
175
+ # Add tags if available
176
+ if parsed_data['tags']:
177
+ post_data['tags'] = parsed_data['tags']
178
+
179
+ # Add category (default to uncategorized)
180
+ post_data['categories'] = [1]
181
+
182
+ # Add featured image
183
+ if media_id:
184
+ post_data['featured_media'] = media_id
185
+
186
+ # Try to set social images
187
+ try:
188
+ # Get media URL
189
+ media_response = requests.get(
190
+ f"{wp_config['url']}/wp-json/wp/v2/media/{media_id}",
191
+ auth=(wp_config['username'], wp_config['password'])
192
+ )
193
+ if media_response.status_code == 200:
194
+ media_url = media_response.json().get('source_url')
195
+ post_data['meta']['rank_math_facebook_image'] = media_url
196
+ post_data['meta']['rank_math_twitter_image'] = media_url
197
+ except:
198
+ pass
199
+
200
+ # Send to WordPress
201
+ response = requests.post(
202
+ f"{wp_config['url']}/wp-json/wp/v2/posts",
203
+ auth=(wp_config['username'], wp_config['password']),
204
+ json=post_data,
205
+ headers={'Content-Type': 'application/json'}
206
+ )
207
+
208
+ if response.status_code == 201:
209
+ # Update canonical URL with actual post URL
210
+ post_result = response.json()
211
+ update_data = {
212
+ 'meta': {
213
+ 'rank_math_canonical_url': post_result['link']
214
+ }
215
+ }
216
+
217
+ update_response = requests.post(
218
+ f"{wp_config['url']}/wp-json/wp/v2/posts/{post_result['id']}",
219
+ auth=(wp_config['username'], wp_config['password']),
220
+ json=update_data
221
+ )
222
+
223
+ return post_result
224
+ else:
225
+ return None
226
+
227
+ except Exception as e:
228
+ print(f"Error creating post: {str(e)}")
229
+ return None
230
+
231
+ # ========== GRADIO UI FUNCTIONS ==========
232
+ def parse_content(gemini_content, post_status):
233
+ """Parse Gemini content and return preview"""
234
+ if not gemini_content.strip():
235
+ return "Please paste content first", "", "", "", "", "", {}
236
+
237
+ parsed = parse_gemini_content(gemini_content, post_status)
238
+
239
+ if 'error' in parsed:
240
+ return parsed['error'], "", "", "", "", "", {}
241
+
242
+ # Format tags for display
243
+ tags_display = ", ".join(parsed['tags'][:6])
244
+
245
+ # Format meta description preview
246
+ meta_preview = parsed['meta_description'][:100] + "..." if len(parsed['meta_description']) > 100 else parsed['meta_description']
247
+
248
+ # Create rank math preview text
249
+ rank_math_preview = "\n".join([f"{key}: {value}" for key, value in parsed['rank_math_meta'].items()][:5])
250
+
251
+ return (
252
+ f"✅ Parsed: {parsed['seo_title']}",
253
+ parsed['seo_title'],
254
+ parsed['url_slug'],
255
+ meta_preview,
256
+ parsed['primary_keyword'],
257
+ tags_display,
258
+ f"Content Length: {len(parsed['content'])} chars"
259
+ ), parsed
260
+
261
+ def publish_post(parsed_data, wp_config, image_file, post_status, wp_url, wp_username, wp_password):
262
+ """Publish post to WordPress"""
263
+ if not parsed_data:
264
+ return "❌ No parsed content available. Please parse content first.", None
265
+
266
+ # Use stored config or provided inputs
267
+ if not wp_config:
268
+ wp_config = {
269
+ 'url': wp_url.rstrip('/') if wp_url else '',
270
+ 'username': wp_username,
271
+ 'password': wp_password,
272
+ 'status': post_status
273
+ }
274
+
275
+ # Validate config
276
+ if not all([wp_config['url'], wp_config['username'], wp_config['password']]):
277
+ return "❌ WordPress configuration incomplete. Please provide URL, username, and password.", None
278
+
279
+ try:
280
+ yield "🖼️ Uploading image to WordPress...", None
281
+
282
+ # Step 1: Upload image
283
+ media_id = None
284
+ if image_file:
285
+ media_id = upload_image_to_wordpress(
286
+ image_file,
287
+ wp_config,
288
+ parsed_data['url_slug']
289
+ )
290
+
291
+ yield "📤 Creating post with Rank Math meta...", None
292
+
293
+ # Step 2: Create post
294
+ result = create_wordpress_post(
295
+ parsed_data,
296
+ wp_config,
297
+ media_id,
298
+ post_status
299
+ )
300
+
301
+ if result:
302
+ # Create download data
303
+ export_data = {
304
+ 'post_id': result['id'],
305
+ 'title': result['title']['rendered'],
306
+ 'link': result['link'],
307
+ 'status': result['status'],
308
+ 'slug': result['slug'],
309
+ 'published_at': datetime.now().isoformat()
310
+ }
311
+
312
+ # Save JSON to file
313
+ json_file = f"post_{result['id']}.json"
314
+ with open(json_file, 'w') as f:
315
+ json.dump(export_data, f, indent=2)
316
+
317
+ success_msg = f"""
318
+ ✅ Post published successfully!
319
+
320
+ Post ID: {result['id']}
321
+ Status: {result['status']}
322
+ Date: {result['date'][:10]}
323
+ Link: {result['link']}
324
+
325
+ Click the download button to get post data.
326
+ """
327
+
328
+ yield success_msg, json_file
329
+ else:
330
+ yield "❌ Failed to publish post. Check WordPress configuration and try again.", None
331
+
332
+ except Exception as e:
333
+ yield f"❌ Error: {str(e)}", None
334
+
335
+ def clear_all():
336
+ """Clear all inputs"""
337
+ return (
338
+ "", # gemini_content
339
+ None, # image
340
+ "https://cdgarment.com", # wp_url
341
+ "", # wp_username
342
+ "", # wp_password
343
+ "draft", # post_status
344
+ "", # parse_status
345
+ "", # seo_title
346
+ "", # url_slug
347
+ "", # meta_description
348
+ "", # primary_keyword
349
+ "", # tags
350
+ "", # content_length
351
+ "", # publish_status
352
+ None, # download_file
353
+ )
354
+
355
+ # ========== GRADIO UI ==========
356
+ with gr.Blocks(
357
+ title="CdGarment WordPress Publisher",
358
+ theme=gr.themes.Soft(
359
+ primary_hue="purple",
360
+ secondary_hue="green",
361
+ ),
362
+ css="""
363
+ .gradio-container {
364
+ max-width: 1400px !important;
365
+ }
366
+ .container {
367
+ padding: 20px;
368
+ }
369
+ .header {
370
+ text-align: center;
371
+ margin-bottom: 20px;
372
+ }
373
+ .header h1 {
374
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
375
+ -webkit-background-clip: text;
376
+ -webkit-text-fill-color: transparent;
377
+ font-size: 2.5rem;
378
+ font-weight: 800;
379
+ margin-bottom: 10px;
380
+ }
381
+ .badge {
382
+ background: linear-gradient(135deg, #fee2e2 0%, #fecaca 100%);
383
+ color: #991B1B;
384
+ padding: 8px 16px;
385
+ border-radius: 20px;
386
+ font-weight: 600;
387
+ display: inline-block;
388
+ margin-bottom: 20px;
389
+ }
390
+ .paste-box {
391
+ border: 2px solid #3B82F6;
392
+ border-radius: 10px;
393
+ padding: 20px;
394
+ background-color: #F8FAFC;
395
+ min-height: 500px;
396
+ }
397
+ .preview-box {
398
+ background-color: #f8f9fa;
399
+ border-radius: 10px;
400
+ padding: 15px;
401
+ margin: 10px 0;
402
+ }
403
+ .status-indicator {
404
+ display: inline-block;
405
+ padding: 5px 10px;
406
+ border-radius: 5px;
407
+ font-weight: bold;
408
+ margin: 2px;
409
+ }
410
+ .success {
411
+ background-color: #d1fae5;
412
+ color: #065f46;
413
+ }
414
+ .warning {
415
+ background-color: #fef3c7;
416
+ color: #92400e;
417
+ }
418
+ .error {
419
+ background-color: #fee2e2;
420
+ color: #991b1b;
421
+ }
422
+ """
423
+ ) as demo:
424
+
425
+ # State variables
426
+ parsed_data_state = gr.State(None)
427
+ wp_config_state = gr.State(None)
428
+
429
+ # Header
430
+ gr.HTML("""
431
+ <div class="header">
432
+ <h1>🏭 CdGarment WordPress Publisher</h1>
433
+ <div class="badge">✓ Optimized for Rank Math SEO</div>
434
+ </div>
435
+ """)
436
+
437
+ # Two main columns
438
+ with gr.Row():
439
+ with gr.Column(scale=2):
440
+ with gr.Group():
441
+ gr.Markdown("### 📋 Paste Gemini Content")
442
+ gr.Markdown("Include SEO package and article")
443
+
444
+ # Example content
445
+ example_content = """SEO Toolkit: Article #20
446
+ Element Suggestion
447
+ Primary Keyword: Apparel ODM Design Services
448
+ SEO Title: The Creative Engine: Inside CdGarment's Powerful R&D Team for ODM Success
449
+ Meta Description: Elevate your brand with CdGarment's R&D team. We provide expert ODM services, from trend analysis to prototype development, ensuring your vision becomes reality.
450
+ Tags: apparel ODM partner, fashion R&D team, original design manufacturer, garment prototype development, CdGarment R&D, fashion trend development
451
+ 📝 Article #20: Full Content
452
+ The Creative Engine: How CdGarment's R&D Team Powers Global ODM Success...
453
+
454
+ [Paste your complete Gemini output here]"""
455
+
456
+ gemini_content = gr.Textbox(
457
+ label="Gemini Content",
458
+ value=example_content,
459
+ lines=20,
460
+ placeholder="Paste your complete Gemini output here with all formatting",
461
+ elem_classes="paste-box"
462
+ )
463
+
464
+ with gr.Row():
465
+ parse_btn = gr.Button("🔍 Parse Content", variant="primary", scale=2)
466
+ clear_btn = gr.Button("🗑️ Clear", variant="secondary", scale=1)
467
+
468
+ with gr.Column(scale=1):
469
+ # Image upload section
470
+ with gr.Group():
471
+ gr.Markdown("### 🖼️ Image & Upload")
472
+ image_input = gr.File(
473
+ label="Upload Image",
474
+ file_types=["image"],
475
+ type="filepath"
476
+ )
477
+ image_preview = gr.Image(
478
+ label="Image Preview",
479
+ height=200,
480
+ visible=False
481
+ )
482
+ image_description = gr.Textbox(
483
+ label="Image Description",
484
+ placeholder="Auto-generated description will appear here",
485
+ lines=3
486
+ )
487
+
488
+ # WordPress settings section
489
+ with gr.Group():
490
+ gr.Markdown("### ⚙️ WordPress Settings")
491
+
492
+ # Load environment variables
493
+ env_config = load_environment_variables()
494
+
495
+ wp_url = gr.Textbox(
496
+ label="WordPress URL",
497
+ value=env_config['url'],
498
+ placeholder="https://cdgarment.com"
499
+ )
500
+ wp_username = gr.Textbox(
501
+ label="Username",
502
+ value=env_config['username'],
503
+ placeholder="admin"
504
+ )
505
+ wp_password = gr.Textbox(
506
+ label="Application Password",
507
+ value=env_config['password'],
508
+ type="password",
509
+ placeholder="••••••••"
510
+ )
511
+ post_status = gr.Radio(
512
+ label="Post Status",
513
+ choices=["draft", "publish"],
514
+ value=env_config['status']
515
+ )
516
+
517
+ save_config_btn = gr.Button("💾 Save Configuration", variant="secondary")
518
+
519
+ # Parse status and preview
520
+ parse_status = gr.Textbox(label="Parse Status", visible=False)
521
+
522
+ with gr.Row(visible=False) as preview_row:
523
+ with gr.Column():
524
+ seo_title = gr.Textbox(label="SEO Title")
525
+ url_slug = gr.Textbox(label="URL Slug")
526
+
527
+ with gr.Column():
528
+ meta_description = gr.Textbox(label="Meta Description")
529
+ primary_keyword = gr.Textbox(label="Primary Keyword")
530
+
531
+ with gr.Column():
532
+ tags = gr.Textbox(label="Tags")
533
+ content_length = gr.Textbox(label="Content Length")
534
+
535
+ # Publish section
536
+ gr.Markdown("---")
537
+ gr.Markdown("### 🚀 Ready to Publish")
538
+
539
+ # Status indicators
540
+ with gr.Row():
541
+ content_status = gr.HTML("<span class='status-indicator warning'>❌ Content</span>")
542
+ image_status = gr.HTML("<span class='status-indicator warning'>⚠️ Image Optional</span>")
543
+ wp_status = gr.HTML("<span class='status-indicator warning'>❌ WordPress</span>")
544
+ status_indicator = gr.HTML("<span class='status-indicator'>📊 Status: DRAFT</span>")
545
+
546
+ publish_btn = gr.Button("🚀 PUSH TO WORDPRESS", variant="primary", size="lg")
547
+
548
+ # Publish results
549
+ publish_status = gr.Textbox(label="Publish Status", lines=5)
550
+ download_file = gr.File(label="Download Post Data", visible=False)
551
+
552
+ # Footer
553
+ gr.Markdown("---")
554
+ gr.Markdown(f"CdGarment WordPress Publisher • {datetime.now().year}")
555
+
556
+ # ========== EVENT HANDLERS ==========
557
+
558
+ def update_status_indicators(has_content, has_image, has_wp_config, post_status_value):
559
+ """Update status indicators"""
560
+ content_html = f"<span class='status-indicator success'>✅ Content</span>" if has_content else f"<span class='status-indicator error'>❌ Content</span>"
561
+ image_html = f"<span class='status-indicator success'>✅ Image</span>" if has_image else f"<span class='status-indicator warning'>⚠️ Image Optional</span>"
562
+ wp_html = f"<span class='status-indicator success'>✅ WordPress</span>" if has_wp_config else f"<span class='status-indicator error'>❌ WordPress</span>"
563
+ status_html = f"<span class='status-indicator'>📊 Status: {post_status_value.upper()}</span>"
564
+
565
+ return content_html, image_html, wp_html, status_html
566
+
567
+ def check_wp_config(wp_url_val, wp_username_val, wp_password_val):
568
+ """Check if WordPress config is complete"""
569
+ return all([wp_url_val, wp_username_val, wp_password_val])
570
+
571
+ # Image upload handler
572
+ def update_image_preview(image_file):
573
+ if image_file:
574
+ return gr.update(visible=True, value=image_file), True
575
+ return gr.update(visible=False), False
576
+
577
+ # Parse button handler
578
+ def on_parse_click(gemini_content_val, post_status_val):
579
+ if not gemini_content_val.strip():
580
+ return (
581
+ gr.update(value="Please paste content first", visible=True),
582
+ gr.update(visible=False),
583
+ "", "", "", "", "",
584
+ None,
585
+ *update_status_indicators(False, False, False, post_status_val)
586
+ )
587
+
588
+ result, parsed = parse_content(gemini_content_val, post_status_val)
589
+
590
+ if isinstance(result, tuple):
591
+ parse_status_val, seo_title_val, url_slug_val, meta_desc_val, keyword_val, tags_val, content_len_val = result
592
+ return (
593
+ gr.update(value=parse_status_val, visible=True),
594
+ gr.update(visible=True),
595
+ seo_title_val,
596
+ url_slug_val,
597
+ meta_desc_val,
598
+ keyword_val,
599
+ tags_val,
600
+ content_len_val,
601
+ parsed,
602
+ *update_status_indicators(True, False, False, post_status_val)
603
+ )
604
+ else:
605
+ return (
606
+ gr.update(value=result, visible=True),
607
+ gr.update(visible=False),
608
+ "", "", "", "", "",
609
+ None,
610
+ *update_status_indicators(False, False, False, post_status_val)
611
+ )
612
+
613
+ # Save config handler
614
+ def on_save_config(wp_url_val, wp_username_val, wp_password_val, post_status_val):
615
+ if all([wp_url_val, wp_username_val, wp_password_val]):
616
+ wp_config = {
617
+ 'url': wp_url_val.rstrip('/'),
618
+ 'username': wp_username_val,
619
+ 'password': wp_password_val,
620
+ 'status': post_status_val
621
+ }
622
+ return (
623
+ gr.update(value="✅ Configuration saved!"),
624
+ wp_config,
625
+ *update_status_indicators(False, False, True, post_status_val)
626
+ )
627
+ return (
628
+ gr.update(value="⚠️ Please fill all WordPress configuration fields"),
629
+ None,
630
+ *update_status_indicators(False, False, False, post_status_val)
631
+ )
632
+
633
+ # Publish button handler
634
+ def on_publish_click(parsed_data, wp_config, image_file, post_status_val, wp_url_val, wp_username_val, wp_password_val):
635
+ if not parsed_data:
636
+ yield (
637
+ gr.update(value="❌ No parsed content available. Please parse content first"),
638
+ gr.update(visible=False),
639
+ *update_status_indicators(False, bool(image_file), bool(wp_config), post_status_val)
640
+ )
641
+ return
642
+
643
+ wp_config_to_use = wp_config or {
644
+ 'url': wp_url_val.rstrip('/') if wp_url_val else '',
645
+ 'username': wp_username_val,
646
+ 'password': wp_password_val,
647
+ 'status': post_status_val
648
+ }
649
+
650
+ # Update status during processing
651
+ yield (
652
+ gr.update(value="🖼️ Uploading image to WordPress..."),
653
+ gr.update(visible=False),
654
+ *update_status_indicators(True, bool(image_file), True, post_status_val)
655
+ )
656
+
657
+ # Simulate progress (Gradio doesn't have built-in progress bars for streaming)
658
+ import time
659
+ time.sleep(1)
660
+
661
+ # Call the publish function
662
+ publish_result = list(publish_post(parsed_data, wp_config_to_use, image_file, post_status_val, wp_url_val, wp_username_val, wp_password_val))
663
+
664
+ if len(publish_result) > 1:
665
+ status_msg, download_file_val = publish_result[-1]
666
+ if download_file_val:
667
+ yield (
668
+ gr.update(value=status_msg),
669
+ gr.update(value=download_file_val, visible=True),
670
+ *update_status_indicators(True, bool(image_file), True, post_status_val)
671
+ )
672
+ else:
673
+ yield (
674
+ gr.update(value=status_msg),
675
+ gr.update(visible=False),
676
+ *update_status_indicators(True, bool(image_file), True, post_status_val)
677
+ )
678
+
679
+ # Update status indicators when inputs change
680
+ def update_indicators_on_change(gemini_content_val, image_file, wp_url_val, wp_username_val, wp_password_val, post_status_val, parsed_data):
681
+ has_content = bool(parsed_data) or bool(gemini_content_val and gemini_content_val.strip())
682
+ has_image = bool(image_file)
683
+ has_wp = check_wp_config(wp_url_val, wp_username_val, wp_password_val)
684
+ return update_status_indicators(has_content, has_image, has_wp, post_status_val)
685
+
686
+ # ========== BIND EVENT HANDLERS ==========
687
+
688
+ # Image upload
689
+ image_input.change(
690
+ update_image_preview,
691
+ inputs=[image_input],
692
+ outputs=[image_preview, image_status]
693
+ )
694
+
695
+ # Parse button
696
+ parse_btn.click(
697
+ on_parse_click,
698
+ inputs=[gemini_content, post_status],
699
+ outputs=[
700
+ parse_status,
701
+ preview_row,
702
+ seo_title,
703
+ url_slug,
704
+ meta_description,
705
+ primary_keyword,
706
+ tags,
707
+ content_length,
708
+ parsed_data_state,
709
+ content_status,
710
+ image_status,
711
+ wp_status,
712
+ status_indicator
713
+ ]
714
+ )
715
+
716
+ # Save config button
717
+ save_config_btn.click(
718
+ on_save_config,
719
+ inputs=[wp_url, wp_username, wp_password, post_status],
720
+ outputs=[parse_status, wp_config_state, content_status, image_status, wp_status, status_indicator]
721
+ )
722
+
723
+ # Publish button
724
+ publish_btn.click(
725
+ on_publish_click,
726
+ inputs=[parsed_data_state, wp_config_state, image_input, post_status, wp_url, wp_username, wp_password],
727
+ outputs=[publish_status, download_file, content_status, image_status, wp_status, status_indicator]
728
+ )
729
+
730
+ # Clear button
731
+ clear_btn.click(
732
+ clear_all,
733
+ outputs=[
734
+ gemini_content,
735
+ image_input,
736
+ wp_url,
737
+ wp_username,
738
+ wp_password,
739
+ post_status,
740
+ parse_status,
741
+ seo_title,
742
+ url_slug,
743
+ meta_description,
744
+ primary_keyword,
745
+ tags,
746
+ content_length,
747
+ publish_status,
748
+ download_file
749
+ ]
750
+ )
751
+
752
+ # Update status indicators on input changes
753
+ for input_component in [gemini_content, image_input, wp_url, wp_username, wp_password, post_status]:
754
+ input_component.change(
755
+ update_indicators_on_change,
756
+ inputs=[gemini_content, image_input, wp_url, wp_username, wp_password, post_status, parsed_data_state],
757
+ outputs=[content_status, image_status, wp_status, status_indicator]
758
+ )
759
+
760
+ # Launch the Gradio app
761
+ if __name__ == "__main__":
762
+ demo.launch(
763
+ server_name="0.0.0.0",
764
+ server_port=7860,
765
+ share=False,
766
+ debug=True
767
+ )