Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,63 +1,7 @@
|
|
| 1 |
-
#
|
| 2 |
-
"""Seo_Blog_Finale.ipynb
|
| 3 |
-
|
| 4 |
-
Automatically generated by Colab.
|
| 5 |
-
|
| 6 |
-
Original file is located at
|
| 7 |
-
https://colab.research.google.com/drive/1QI2D7IKD5MNQKverCCzA9-Mq_pkxD4PX
|
| 8 |
-
"""
|
| 9 |
-
|
| 10 |
-
# ======================== INSTALL DEPENDENCIES ========================
|
| 11 |
-
# Install required packages
|
| 12 |
-
def install_package(package):
|
| 13 |
-
try:
|
| 14 |
-
import subprocess
|
| 15 |
-
import sys
|
| 16 |
-
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
|
| 17 |
-
print(f"β
{package} installed successfully")
|
| 18 |
-
except:
|
| 19 |
-
print(f"β οΈ Could not install {package}")
|
| 20 |
-
|
| 21 |
-
packages = [
|
| 22 |
-
"openai", "requests", "beautifulsoup4", "pandas", "gradio", "openpyxl", "spacy", "python-docx", "google-search-results"
|
| 23 |
-
]
|
| 24 |
-
for package in packages:
|
| 25 |
-
try:
|
| 26 |
-
if package == "openai":
|
| 27 |
-
from openai import OpenAI
|
| 28 |
-
elif package == "requests":
|
| 29 |
-
import requests
|
| 30 |
-
elif package == "beautifulsoup4":
|
| 31 |
-
import bs4
|
| 32 |
-
elif package == "pandas":
|
| 33 |
-
import pandas
|
| 34 |
-
elif package == "gradio":
|
| 35 |
-
import gradio
|
| 36 |
-
elif package == "openpyxl":
|
| 37 |
-
import openpyxl
|
| 38 |
-
elif package == "spacy":
|
| 39 |
-
import spacy
|
| 40 |
-
elif package == "python-docx":
|
| 41 |
-
from docx import Document
|
| 42 |
-
elif package == "google-search-results":
|
| 43 |
-
from serpapi import GoogleSearch
|
| 44 |
-
except ImportError:
|
| 45 |
-
install_package(package)
|
| 46 |
-
|
| 47 |
-
# Download spaCy model if needed
|
| 48 |
-
try:
|
| 49 |
-
import spacy
|
| 50 |
-
nlp = spacy.load("en_core_web_sm")
|
| 51 |
-
except:
|
| 52 |
-
print("Installing spaCy English model...")
|
| 53 |
-
import subprocess
|
| 54 |
-
import sys
|
| 55 |
-
subprocess.check_call([sys.executable, "-m", "spacy", "download", "en_core_web_sm"])
|
| 56 |
-
nlp = spacy.load("en_core_web_sm")
|
| 57 |
-
|
| 58 |
import subprocess
|
| 59 |
import sys
|
| 60 |
-
import os
|
| 61 |
import re
|
| 62 |
import time
|
| 63 |
import json
|
|
@@ -77,6 +21,10 @@ from docx.oxml import OxmlElement
|
|
| 77 |
from docx.oxml.ns import qn
|
| 78 |
from itertools import cycle
|
| 79 |
from serpapi import GoogleSearch
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
# ======================== CONFIGURE OPENAI & SERPAPI ========================
|
| 82 |
# MODIFIED: Get API keys from Hugging Face Secrets and add error handling
|
|
@@ -92,7 +40,6 @@ if not serpapi_key:
|
|
| 92 |
client = OpenAI(api_key=openai_api_key)
|
| 93 |
SERPAPI_KEY = serpapi_key
|
| 94 |
|
| 95 |
-
|
| 96 |
# ======================== BLOG CONTENT TYPES ========================
|
| 97 |
BLOG_TYPES = {
|
| 98 |
"How-To Guide π": "step-by-step tutorials",
|
|
@@ -141,6 +88,7 @@ Now, generate the FAQs for the current blog topic following these rules and the
|
|
| 141 |
"""
|
| 142 |
|
| 143 |
BLOG_STRUCTURES = {
|
|
|
|
| 144 |
"How-To Guide": [
|
| 145 |
("Intro Hook", "Problem Hook", 40, 60, "Hook the reader by presenting a common, relatable problem they're struggling with. Frame it as a challenge that many in their position face."),
|
| 146 |
("Intro Hook", "Promise the Outcome", 20, 30, "In one single, powerful sentence, promise the specific, tangible skill the reader will have after finishing this guide. Be extremely concise. Example: 'By the end of this guide, you will be able to build a high-performing global team from scratch.'"),
|
|
@@ -320,6 +268,7 @@ BLOG_STRUCTURES = {
|
|
| 320 |
}
|
| 321 |
|
| 322 |
# ======================== RESEARCH FUNCTIONS ========================
|
|
|
|
| 323 |
def search_web(query): # This is now a fallback
|
| 324 |
try:
|
| 325 |
search_query = quote_plus(query)
|
|
@@ -776,7 +725,7 @@ def format_faq_content(text):
|
|
| 776 |
answer = line[2:].strip()
|
| 777 |
qa_pairs.append(f"Q. {current_q}\nA. {answer}")
|
| 778 |
current_q = None # Reset to find the next question
|
| 779 |
-
|
| 780 |
return "\n\n".join(qa_pairs)
|
| 781 |
|
| 782 |
|
|
@@ -906,17 +855,17 @@ class WordDocumentGenerator:
|
|
| 906 |
for _, item_row in item_sections.iterrows():
|
| 907 |
persona = next(self.ai_personas)
|
| 908 |
yield f" -> Analyzing Item: '{item_row['Section Content'][:40]}...' (as {persona.split(',')[0]})"
|
| 909 |
-
|
| 910 |
content = item_row['Section Content']
|
| 911 |
# Extract the actual generated title from the first line of the content
|
| 912 |
generated_title = content.split('\n')[0].strip() if content else 'Untitled Item'
|
| 913 |
-
|
| 914 |
# Get the analysis for 'ideal_for' and 'business_benefit' from the AI
|
| 915 |
details = self._extract_item_details(content, persona)
|
| 916 |
-
|
| 917 |
# OVERRIDE the name with the actual generated title to ensure consistency
|
| 918 |
details['name'] = generated_title
|
| 919 |
-
|
| 920 |
listicle_items_data.append(details)
|
| 921 |
|
| 922 |
toc_items = []
|
|
@@ -975,11 +924,11 @@ class WordDocumentGenerator:
|
|
| 975 |
continue
|
| 976 |
|
| 977 |
cleaned_content = clean_blog_content(section_content)
|
| 978 |
-
|
| 979 |
# --- MODIFIED: Apply special FAQ formatting function ---
|
| 980 |
if section_name == "FAQs":
|
| 981 |
cleaned_content = format_faq_content(cleaned_content)
|
| 982 |
-
|
| 983 |
if section_name == "At-a-Glance Matrix" and "Comparison" in content_type:
|
| 984 |
header, data = parse_comparison_table(cleaned_content)
|
| 985 |
if data: self.add_comparison_table(header, data); self.doc.add_paragraph(); continue
|
|
@@ -1044,6 +993,7 @@ def process_excel_to_word(excel_file_path):
|
|
| 1044 |
|
| 1045 |
# ======================== GRADIO INTERFACE ========================
|
| 1046 |
def create_interface():
|
|
|
|
| 1047 |
generator = BlogGenerator()
|
| 1048 |
with gr.Blocks(title="π― Digiworks Unified Blog Generator", theme=gr.themes.Soft()) as interface:
|
| 1049 |
gr.HTML("""<div style='text-align: center; padding: 25px; background: linear-gradient(135deg, #1a365d 0%, #2d3748 100%); color: white; margin-bottom: 25px; border-radius: 15px;'><h1 style='margin: 0; font-size: 2.5em; font-weight: 900;'>π― Digiworks Unified Blog Generator</h1><p style='margin: 15px 0 0 0; font-size: 1.2em;'>Idea Generation β Blog Post Creation β Word Document Formatting</p></div>""")
|
|
@@ -1098,134 +1048,135 @@ def create_interface():
|
|
| 1098 |
download_word_btn = gr.File(label="π₯ Download Formatted Word Document", visible=False)
|
| 1099 |
gr.HTML("""<div style='background: #065f46; color: white; padding: 15px; border-radius: 8px; text-align: center; margin-left: 15px; border: 1px solid #10b981;'><strong style='color: #6ee7b7;'>π‘ WordPress Tip:</strong><span style='color: white;'>Copy sections directly from Word to WordPress editor!</span></div>""")
|
| 1100 |
|
| 1101 |
-
|
| 1102 |
-
|
| 1103 |
-
|
| 1104 |
-
|
| 1105 |
-
|
| 1106 |
-
|
| 1107 |
-
|
| 1108 |
-
|
| 1109 |
-
|
| 1110 |
-
|
| 1111 |
-
|
| 1112 |
-
|
| 1113 |
-
|
| 1114 |
-
|
| 1115 |
-
|
| 1116 |
-
|
| 1117 |
-
|
| 1118 |
-
|
| 1119 |
-
|
| 1120 |
-
|
| 1121 |
-
|
| 1122 |
-
|
| 1123 |
-
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
| 1131 |
-
|
| 1132 |
-
|
| 1133 |
-
|
| 1134 |
-
|
| 1135 |
-
|
| 1136 |
-
|
| 1137 |
-
|
| 1138 |
-
|
| 1139 |
-
|
| 1140 |
-
|
| 1141 |
-
|
| 1142 |
-
|
| 1143 |
-
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
|
| 1148 |
-
|
| 1149 |
-
|
| 1150 |
-
|
| 1151 |
-
|
| 1152 |
-
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
|
| 1157 |
-
|
| 1158 |
-
|
| 1159 |
-
|
| 1160 |
-
|
| 1161 |
-
|
| 1162 |
-
|
| 1163 |
-
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
|
| 1172 |
-
|
| 1173 |
-
|
| 1174 |
-
|
| 1175 |
-
|
| 1176 |
-
|
| 1177 |
-
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
| 1188 |
-
|
| 1189 |
-
|
| 1190 |
-
|
| 1191 |
-
|
| 1192 |
-
|
| 1193 |
-
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
|
| 1202 |
-
|
| 1203 |
-
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
|
| 1209 |
-
|
| 1210 |
-
|
| 1211 |
-
|
| 1212 |
-
|
| 1213 |
-
|
| 1214 |
-
|
| 1215 |
-
|
| 1216 |
-
|
| 1217 |
-
|
| 1218 |
-
|
| 1219 |
-
|
| 1220 |
-
|
| 1221 |
-
|
| 1222 |
-
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
|
| 1228 |
-
|
|
|
|
| 1229 |
|
| 1230 |
# ======================== MAIN EXECUTION ========================
|
| 1231 |
if __name__ == "__main__":
|
|
@@ -1239,7 +1190,6 @@ if __name__ == "__main__":
|
|
| 1239 |
print("π Optimized comparison tables for 'At-a-Glance Matrix' sections")
|
| 1240 |
print("="*70)
|
| 1241 |
interface = create_interface()
|
| 1242 |
-
# MODIFIED:
|
| 1243 |
-
interface.launch(auth=("your_username", "your_password")
|
| 1244 |
-
print("β
Unified blog generator launched!")
|
| 1245 |
-
|
|
|
|
| 1 |
+
# MODIFIED: Removed all runtime installation code. Dependencies are now in requirements.txt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
import subprocess
|
| 3 |
import sys
|
| 4 |
+
import os
|
| 5 |
import re
|
| 6 |
import time
|
| 7 |
import json
|
|
|
|
| 21 |
from docx.oxml.ns import qn
|
| 22 |
from itertools import cycle
|
| 23 |
from serpapi import GoogleSearch
|
| 24 |
+
import spacy # MODIFIED: Directly import spacy
|
| 25 |
+
|
| 26 |
+
# MODIFIED: Load the spacy model directly, as it's installed via requirements.txt
|
| 27 |
+
nlp = spacy.load("en_core_web_sm")
|
| 28 |
|
| 29 |
# ======================== CONFIGURE OPENAI & SERPAPI ========================
|
| 30 |
# MODIFIED: Get API keys from Hugging Face Secrets and add error handling
|
|
|
|
| 40 |
client = OpenAI(api_key=openai_api_key)
|
| 41 |
SERPAPI_KEY = serpapi_key
|
| 42 |
|
|
|
|
| 43 |
# ======================== BLOG CONTENT TYPES ========================
|
| 44 |
BLOG_TYPES = {
|
| 45 |
"How-To Guide π": "step-by-step tutorials",
|
|
|
|
| 88 |
"""
|
| 89 |
|
| 90 |
BLOG_STRUCTURES = {
|
| 91 |
+
# ... (Your entire BLOG_STRUCTURES dictionary remains here, unchanged)
|
| 92 |
"How-To Guide": [
|
| 93 |
("Intro Hook", "Problem Hook", 40, 60, "Hook the reader by presenting a common, relatable problem they're struggling with. Frame it as a challenge that many in their position face."),
|
| 94 |
("Intro Hook", "Promise the Outcome", 20, 30, "In one single, powerful sentence, promise the specific, tangible skill the reader will have after finishing this guide. Be extremely concise. Example: 'By the end of this guide, you will be able to build a high-performing global team from scratch.'"),
|
|
|
|
| 268 |
}
|
| 269 |
|
| 270 |
# ======================== RESEARCH FUNCTIONS ========================
|
| 271 |
+
# ... (All your functions from search_web down to the line before WordDocumentGenerator remain here, unchanged)
|
| 272 |
def search_web(query): # This is now a fallback
|
| 273 |
try:
|
| 274 |
search_query = quote_plus(query)
|
|
|
|
| 725 |
answer = line[2:].strip()
|
| 726 |
qa_pairs.append(f"Q. {current_q}\nA. {answer}")
|
| 727 |
current_q = None # Reset to find the next question
|
| 728 |
+
|
| 729 |
return "\n\n".join(qa_pairs)
|
| 730 |
|
| 731 |
|
|
|
|
| 855 |
for _, item_row in item_sections.iterrows():
|
| 856 |
persona = next(self.ai_personas)
|
| 857 |
yield f" -> Analyzing Item: '{item_row['Section Content'][:40]}...' (as {persona.split(',')[0]})"
|
| 858 |
+
|
| 859 |
content = item_row['Section Content']
|
| 860 |
# Extract the actual generated title from the first line of the content
|
| 861 |
generated_title = content.split('\n')[0].strip() if content else 'Untitled Item'
|
| 862 |
+
|
| 863 |
# Get the analysis for 'ideal_for' and 'business_benefit' from the AI
|
| 864 |
details = self._extract_item_details(content, persona)
|
| 865 |
+
|
| 866 |
# OVERRIDE the name with the actual generated title to ensure consistency
|
| 867 |
details['name'] = generated_title
|
| 868 |
+
|
| 869 |
listicle_items_data.append(details)
|
| 870 |
|
| 871 |
toc_items = []
|
|
|
|
| 924 |
continue
|
| 925 |
|
| 926 |
cleaned_content = clean_blog_content(section_content)
|
| 927 |
+
|
| 928 |
# --- MODIFIED: Apply special FAQ formatting function ---
|
| 929 |
if section_name == "FAQs":
|
| 930 |
cleaned_content = format_faq_content(cleaned_content)
|
| 931 |
+
|
| 932 |
if section_name == "At-a-Glance Matrix" and "Comparison" in content_type:
|
| 933 |
header, data = parse_comparison_table(cleaned_content)
|
| 934 |
if data: self.add_comparison_table(header, data); self.doc.add_paragraph(); continue
|
|
|
|
| 993 |
|
| 994 |
# ======================== GRADIO INTERFACE ========================
|
| 995 |
def create_interface():
|
| 996 |
+
# ... (Your entire Gradio interface layout remains here, unchanged)
|
| 997 |
generator = BlogGenerator()
|
| 998 |
with gr.Blocks(title="π― Digiworks Unified Blog Generator", theme=gr.themes.Soft()) as interface:
|
| 999 |
gr.HTML("""<div style='text-align: center; padding: 25px; background: linear-gradient(135deg, #1a365d 0%, #2d3748 100%); color: white; margin-bottom: 25px; border-radius: 15px;'><h1 style='margin: 0; font-size: 2.5em; font-weight: 900;'>π― Digiworks Unified Blog Generator</h1><p style='margin: 15px 0 0 0; font-size: 1.2em;'>Idea Generation β Blog Post Creation β Word Document Formatting</p></div>""")
|
|
|
|
| 1048 |
download_word_btn = gr.File(label="π₯ Download Formatted Word Document", visible=False)
|
| 1049 |
gr.HTML("""<div style='background: #065f46; color: white; padding: 15px; border-radius: 8px; text-align: center; margin-left: 15px; border: 1px solid #10b981;'><strong style='color: #6ee7b7;'>π‘ WordPress Tip:</strong><span style='color: white;'>Copy sections directly from Word to WordPress editor!</span></div>""")
|
| 1050 |
|
| 1051 |
+
# --- HANDLER FUNCTIONS (MODIFIED for Incremental Save) ---
|
| 1052 |
+
def toggle_content_type(mode): return gr.update(visible=(mode == "Single Type"))
|
| 1053 |
+
def handle_idea_generation(mode, content_type_val, num_topics_val):
|
| 1054 |
+
try:
|
| 1055 |
+
if mode == "Single Type": result, df, status = generate_blog_topics(content_type_val, num_topics_val)
|
| 1056 |
+
else: result, df, status = generate_all_types(num_topics_val)
|
| 1057 |
+
download_file, download_visible = None, False
|
| 1058 |
+
if not df.empty:
|
| 1059 |
+
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
| 1060 |
+
filename = f"digiworks_blog_ideas_{timestamp}.xlsx"
|
| 1061 |
+
temp_path = os.path.join(tempfile.gettempdir(), filename)
|
| 1062 |
+
with pd.ExcelWriter(temp_path, engine='openpyxl') as writer: df.to_excel(writer, sheet_name='Blog Ideas', index=False)
|
| 1063 |
+
download_file, download_visible = temp_path, True
|
| 1064 |
+
return status, result, df, gr.update(value=download_file, visible=download_visible)
|
| 1065 |
+
except Exception as e: return f"β Error: {str(e)}", f"β Generation failed: {str(e)}", pd.DataFrame(), gr.update(visible=False)
|
| 1066 |
+
def load_excel_and_create_checklist(file_path):
|
| 1067 |
+
if not file_path: return gr.update(choices=[], value=[]), pd.DataFrame(), "Please upload an Excel file"
|
| 1068 |
+
try:
|
| 1069 |
+
df = pd.read_excel(file_path)
|
| 1070 |
+
required_cols = ['Content Type', 'Headline']
|
| 1071 |
+
if missing_cols := [col for col in required_cols if col not in df.columns]: return gr.update(choices=[], value=[]), pd.DataFrame(), f"Missing columns: {', '.join(missing_cols)}"
|
| 1072 |
+
choices = []
|
| 1073 |
+
for i, row in df.iterrows():
|
| 1074 |
+
content_type = row.get('Content Type', '').replace(' π', '').replace(' π', '').replace(' βοΈ', '').replace(' π', '').replace(' π', '').replace(' π‘', '')
|
| 1075 |
+
headline = row.get('Headline', '')[:60]; keywords = row.get('Keywords', '')[:50]
|
| 1076 |
+
choices.append(f"#{i+1} [{content_type}] {headline}... | Keywords: {keywords}...")
|
| 1077 |
+
return gr.update(choices=choices, value=choices), df, f"β
Loaded {len(df)} topics. Select topics to generate blog posts."
|
| 1078 |
+
except Exception as e: return gr.update(choices=[], value=[]), pd.DataFrame(), f"β Error: {str(e)}"
|
| 1079 |
+
def reset_topic_counter():
|
| 1080 |
+
generator.reset_counter()
|
| 1081 |
+
return "π Topic counter reset to 0. Next generation will start from Topic #1."
|
| 1082 |
+
|
| 1083 |
+
def generate_selected_posts(file_path, selected_choices, df):
|
| 1084 |
+
if not file_path or not selected_choices:
|
| 1085 |
+
yield "Please upload an Excel file and select at least one topic to generate.", pd.DataFrame(), gr.update(visible=False)
|
| 1086 |
+
return
|
| 1087 |
+
all_sections_data, status_log = [], ["π Starting blog post generation..."]
|
| 1088 |
+
latest_file_path = None
|
| 1089 |
+
yield "\n".join(status_log), pd.DataFrame(), gr.update(value=None, visible=False)
|
| 1090 |
+
try:
|
| 1091 |
+
for topic_counter, choice in enumerate(selected_choices, 1):
|
| 1092 |
+
choice_index = int(choice.split('#')[1].split(' ')[0]) - 1
|
| 1093 |
+
if choice_index < len(df):
|
| 1094 |
+
topic_data = df.iloc[choice_index].to_dict()
|
| 1095 |
+
generation_iterator = generator.generate_blog_from_topic_data(topic_data, topic_index=topic_counter)
|
| 1096 |
+
|
| 1097 |
+
topic_sections = []
|
| 1098 |
+
for update_type, update_value, *rest in generation_iterator:
|
| 1099 |
+
if update_type == 'status':
|
| 1100 |
+
message = update_value
|
| 1101 |
+
status_log.append(message)
|
| 1102 |
+
yield "\n".join(status_log), pd.DataFrame(all_sections_data), gr.update(value=latest_file_path, visible=bool(latest_file_path))
|
| 1103 |
+
elif update_type == 'result':
|
| 1104 |
+
sections, msg = update_value, rest[0]
|
| 1105 |
+
if sections:
|
| 1106 |
+
topic_sections.extend(sections)
|
| 1107 |
+
status_log.append(msg)
|
| 1108 |
+
|
| 1109 |
+
if topic_sections:
|
| 1110 |
+
all_sections_data.extend(topic_sections)
|
| 1111 |
+
intermediate_df = pd.DataFrame(all_sections_data)
|
| 1112 |
+
intermediate_file = create_excel_export(all_sections_data)
|
| 1113 |
+
latest_file_path = intermediate_file
|
| 1114 |
+
status_log.append(f"π¦ Intermediate file for Topic #{topic_counter} ready.")
|
| 1115 |
+
yield "\n".join(status_log), intermediate_df, gr.update(value=latest_file_path, visible=True)
|
| 1116 |
+
|
| 1117 |
+
if all_sections_data:
|
| 1118 |
+
final_df = pd.DataFrame(all_sections_data)
|
| 1119 |
+
total_topics, total_sections = len(selected_choices), len(all_sections_data)
|
| 1120 |
+
successful_sections = len([s for s in all_sections_data if s['Word Count'] > 0])
|
| 1121 |
+
total_words = sum(s['Word Count'] for s in all_sections_data if s['Word Count'] > 0)
|
| 1122 |
+
final_status = f"""π ALL TOPICS COMPLETE!\n--------------------\nπ Generated {total_sections} sections across {total_topics} topics\nβ
Successful sections: {successful_sections}/{total_sections}\nπ Total words generated: {total_words:,}\nπ’ Topics indexed: 1-{total_topics}\n\nFull Log:\n""" + "\n".join(status_log)
|
| 1123 |
+
yield final_status, final_df, gr.update(value=latest_file_path, visible=True)
|
| 1124 |
+
else:
|
| 1125 |
+
yield "β No sections were generated.", pd.DataFrame(), gr.update(visible=False)
|
| 1126 |
+
except Exception as e:
|
| 1127 |
+
import traceback
|
| 1128 |
+
tb_str = traceback.format_exc()
|
| 1129 |
+
yield f"β An unexpected error occurred: {str(e)}\n\nTraceback:\n{tb_str}", pd.DataFrame(), gr.update(visible=False)
|
| 1130 |
+
|
| 1131 |
+
def process_excel_file(excel_file):
|
| 1132 |
+
if not excel_file: return "Please upload an Excel file", "", gr.update(visible=False)
|
| 1133 |
+
try:
|
| 1134 |
+
df = pd.read_excel(excel_file); preview = "No content preview available"
|
| 1135 |
+
if not df.empty and 'Section Content' in df.columns:
|
| 1136 |
+
cleaned_sample = clean_blog_content(df.iloc[0]['Section Content'])
|
| 1137 |
+
preview = cleaned_sample[:500] + "..." if len(cleaned_sample) > 500 else cleaned_sample
|
| 1138 |
+
status = f"β
Excel loaded: {len(df)} sections found\nπ Topics: {df['Topic Number'].nunique() if 'Topic Number' in df.columns else 'Unknown'}\nπ Ready to generate Word document"
|
| 1139 |
+
return status, preview, gr.update(visible=False)
|
| 1140 |
+
except Exception as e: return f"β Error reading Excel: {str(e)}", "", gr.update(visible=False)
|
| 1141 |
+
|
| 1142 |
+
def generate_word_document(excel_file):
|
| 1143 |
+
if not excel_file:
|
| 1144 |
+
yield "Please upload an Excel file.", "", gr.update(visible=False)
|
| 1145 |
+
return
|
| 1146 |
+
status_log = ["π Starting Word document generation..."]
|
| 1147 |
+
yield "\n".join(status_log), "", gr.update(visible=False)
|
| 1148 |
+
try:
|
| 1149 |
+
processing_generator = process_excel_to_word(excel_file)
|
| 1150 |
+
for type, val1, *val2 in processing_generator:
|
| 1151 |
+
if type == 'status':
|
| 1152 |
+
status_log.append(val1)
|
| 1153 |
+
yield "\n".join(status_log), "", gr.update(visible=False)
|
| 1154 |
+
elif type == 'complete':
|
| 1155 |
+
word_file_path, result_message = val1, val2[0]
|
| 1156 |
+
df = pd.read_excel(excel_file)
|
| 1157 |
+
preview = "Document generated successfully"
|
| 1158 |
+
if not df.empty and 'Section Content' in df.columns:
|
| 1159 |
+
cleaned_sample = clean_blog_content(df.iloc[0]['Section Content'])
|
| 1160 |
+
preview = cleaned_sample[:500] + "..." if len(cleaned_sample) > 500 else cleaned_sample
|
| 1161 |
+
yield result_message, preview, gr.update(value=word_file_path, visible=True)
|
| 1162 |
+
elif type == 'error':
|
| 1163 |
+
yield val1, "", gr.update(visible=False)
|
| 1164 |
+
except Exception as e:
|
| 1165 |
+
import traceback
|
| 1166 |
+
yield f"β An unexpected error occurred: {str(e)}\n{traceback.format_exc()}", "", gr.update(visible=False)
|
| 1167 |
+
|
| 1168 |
+
# --- EVENT LISTENERS ---
|
| 1169 |
+
mode_choice.change(fn=toggle_content_type, inputs=[mode_choice], outputs=[content_type])
|
| 1170 |
+
generate_ideas_btn.click(fn=handle_idea_generation, inputs=[mode_choice, content_type, num_topics], outputs=[ideas_status, ideas_results, ideas_df, download_ideas_btn])
|
| 1171 |
+
excel_upload.change(fn=load_excel_and_create_checklist, inputs=[excel_upload], outputs=[topics_checklist, loaded_data, posts_status])
|
| 1172 |
+
reset_counter_btn.click(fn=reset_topic_counter, inputs=[], outputs=[posts_status])
|
| 1173 |
+
generate_posts_btn.click(fn=generate_selected_posts, inputs=[excel_upload, topics_checklist, loaded_data], outputs=[posts_status, posts_dataframe, download_posts_btn])
|
| 1174 |
+
word_excel_input.change(fn=process_excel_file, inputs=[word_excel_input], outputs=[word_status, word_preview, download_word_btn])
|
| 1175 |
+
generate_word_btn.click(fn=generate_word_document, inputs=[word_excel_input], outputs=[word_status, word_preview, download_word_btn])
|
| 1176 |
+
gr.HTML("""<div style='text-align: center; padding: 20px; margin-top: 25px; border-top: 1px solid #e0e0e0;'><p style='color: #666; margin: 0;'>π― <strong>Digiworks Unified Blog Generator</strong> | Idea Generation β Blog Post Creation β Word Document Formatting<br><small>Perfect for content teams and marketers</small></p></div>""")
|
| 1177 |
+
|
| 1178 |
+
return interface
|
| 1179 |
+
|
| 1180 |
|
| 1181 |
# ======================== MAIN EXECUTION ========================
|
| 1182 |
if __name__ == "__main__":
|
|
|
|
| 1190 |
print("π Optimized comparison tables for 'At-a-Glance Matrix' sections")
|
| 1191 |
print("="*70)
|
| 1192 |
interface = create_interface()
|
| 1193 |
+
# MODIFIED: Launch call is simplified for Hugging Face deployment
|
| 1194 |
+
interface.launch(auth=("your_username", "your_password"))
|
| 1195 |
+
print("β
Unified blog generator launched!")
|
|
|