BlogAgent / app.py
jsakshi's picture
Update app.py
ef3d0e6 verified
'''import gradio as gr
import os
import time
import requests
import re
import uuid
import markdown
from datetime import datetime
from dotenv import load_dotenv
from huggingface_hub import HfApi, upload_file
load_dotenv()
# Configuration
HF_TOKEN = os.getenv("HF_TOKEN")
HF_USERNAME = "jsakshi"
HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"}
def generate_blog_content(topic):
try:
prompt = f"""Create a detailed, professional blog post about {topic} including:
- A compelling title and subtitle
- An introduction
- 3 main sections with descriptive headings
- Key points and data examples in each section
- A conclusion
Use an informative, professional tone."""
response = requests.post(
"https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2",
headers=HEADERS,
json={"inputs": prompt, "parameters": {"max_length": 2000}}
)
if response.status_code != 200:
return None, f"API Error: {response.text}"
blog_text = response.json()[0]['generated_text']
# Extract title (assuming first line contains the title)
lines = blog_text.split('\n')
title = topic
subtitle = "A comprehensive analysis and exploration"
for line in lines[:5]:
if line.strip() and not line.startswith('#'):
title = line.strip()
break
# Look for a possible subtitle
for line in lines[1:10]:
if line.strip() and line != title and not line.startswith('#'):
subtitle = line.strip()
break
return {
"title": title,
"subtitle": subtitle,
"content": blog_text
}, None
except Exception as e:
return None, f"Error: {str(e)}"
def create_hosted_blog(topic):
try:
# Generate blog content first
content_data, error = generate_blog_content(topic)
if error or not content_data:
return f"Error generating content: {error}", ""
# Create unique space
space_id = f"blog-{uuid.uuid4().hex[:8]}"
space_name = f"{HF_USERNAME}/{space_id}"
# Initialize Hub API
api = HfApi(token=HF_TOKEN)
api.create_repo(
repo_id=space_name,
repo_type="space",
space_sdk="static",
private=False
)
# Generate and upload images
image_paths = []
image_prompts = [
f"Professional illustration about {topic}, integrating real-world images with clean design and minimalist style. "
f"Include conceptual diagrams, flowcharts, or graphs alongside real-world elements to enhance understanding. "
f"Use subtle colors, modern typography, and a well-structured layout for clarity and engagement.",
f"Data visualization or concept diagram related to {topic}, combining infographic elements with real-world scenarios. "
f"Ensure a balance of artistic design and informative content, making it suitable for presentations or reports. "
f"Include 3D renders or photorealistic overlays to improve visualization."
]
for idx, img_prompt in enumerate(image_prompts):
response = requests.post(
"https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0",
headers=HEADERS,
json={"inputs": img_prompt}
)
if response.status_code == 200:
img_filename = f"image_{idx}.png"
upload_file(
path_or_fileobj=response.content,
path_in_repo=img_filename,
repo_id=space_name,
repo_type="space"
)
image_paths.append(img_filename)
time.sleep(2) # Add delay to prevent rate limiting
# Format the current date
current_date = datetime.now().strftime("%B %d, %Y")
# Create HTML using the modern template from the example
title = content_data.get("title", topic)
subtitle = content_data.get("subtitle", "A comprehensive analysis")
content = content_data.get("content", "")
# Process the content to get sections for TOC
sections = []
section_pattern = re.compile(r'^##?\s+(.+)$', re.MULTILINE)
section_matches = section_pattern.findall(content)
for i, section in enumerate(section_matches[:6]):
section_id = section.lower().replace(' ', '-').replace(':', '')
sections.append({
"title": section,
"id": section_id
})
# Convert markdown content to HTML with proper section IDs
html_content = content
for section in sections:
pattern = f"## {section['title']}"
replacement = f"<h2 id=\"{section['id']}\">{section['title']}</h2>"
html_content = html_content.replace(pattern, replacement)
html_content = markdown.markdown(html_content)
# Create complete HTML
complete_html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title}</title>
<style>
:root {{
--primary-color: #2D68C4;
--secondary-color: #f8f9fa;
--text-color: #333;
--light-gray: #e9ecef;
--dark-gray: #495057;
}}
* {{
margin: 0;
padding: 0;
box-sizing: border-box;
}}
body {{
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: #fff;
}}
header {{
background: linear-gradient(135deg, var(--primary-color), #1d4ed8);
color: white;
padding: 2rem 0;
text-align: center;
}}
.container {{
max-width: 1200px;
margin: 0 auto;
padding: 0 2rem;
}}
.blog-header {{
display: flex;
flex-direction: column;
align-items: center;
}}
.blog-title {{
font-size: 2.5rem;
margin-bottom: 1rem;
}}
.blog-subtitle {{
font-size: 1.2rem;
opacity: 0.9;
}}
.blog-meta {{
display: flex;
margin-top: 1rem;
font-size: 0.9rem;
}}
.blog-meta div {{
margin-right: 1.5rem;
display: flex;
align-items: center;
}}
.blog-content {{
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
margin: 3rem 0;
}}
@media (min-width: 768px) {{
.blog-content {{
grid-template-columns: 7fr 3fr;
}}
}}
.main-content {{
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
padding: 2rem;
}}
.sidebar {{
position: sticky;
top: 2rem;
height: fit-content;
}}
.sidebar-section {{
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
padding: 1.5rem;
margin-bottom: 2rem;
}}
.sidebar-title {{
font-size: 1.2rem;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid var(--light-gray);
}}
.toc-list {{
list-style: none;
}}
.toc-list li {{
margin-bottom: 0.5rem;
}}
.toc-list a {{
color: var(--primary-color);
text-decoration: none;
}}
.toc-list a:hover {{
text-decoration: underline;
}}
h1, h2, h3, h4 {{
margin: 1.5rem 0 1rem 0;
line-height: 1.3;
}}
h1 {{
font-size: 2rem;
}}
h2 {{
font-size: 1.75rem;
border-bottom: 2px solid var(--light-gray);
padding-bottom: 0.5rem;
}}
h3 {{
font-size: 1.4rem;
}}
p {{
margin-bottom: 1.5rem;
}}
.blog-image {{
width: 100%;
height: auto;
border-radius: 8px;
margin: 1.5rem 0;
}}
.highlight-box {{
background-color: var(--secondary-color);
border-left: 4px solid var(--primary-color);
padding: 1.5rem;
margin: 1.5rem 0;
border-radius: 0 8px 8px 0;
}}
.highlight-box h4 {{
margin-top: 0;
color: var(--primary-color);
}}
footer {{
background-color: var(--dark-gray);
color: white;
padding: 2rem 0;
margin-top: 3rem;
}}
.footer-content {{
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}}
@media (min-width: 768px) {{
.footer-content {{
flex-direction: row;
justify-content: space-between;
text-align: left;
}}
}}
.footer-links {{
list-style: none;
display: flex;
margin-top: 1rem;
}}
.footer-links li {{
margin-right: 1rem;
}}
.footer-links a {{
color: white;
text-decoration: none;
}}
.footer-links a:hover {{
text-decoration: underline;
}}
</style>
</head>
<body>
<header>
<div class="container">
<div class="blog-header">
<h1 class="blog-title">{title}</h1>
<div class="blog-meta">
<div>Published: {current_date}</div>
<div>Reading time: 8 minutes</div>
</div>
</div>
</div>
</header>
<div class="container">
<div class="blog-content">
<article class="main-content">
{f'<img src="{image_paths[0]}" alt="{topic} illustration" class="blog-image" />' if image_paths else ''}
{html_content}
{f'<img src="{image_paths[1]}" alt="{topic} visualization" class="blog-image" />' if len(image_paths) > 1 else ''}
<div class="highlight-box">
<h4>Key Takeaways</h4>
<p>This article explores the essential aspects of {topic}, providing insights into current trends, challenges, and future opportunities in this field.</p>
</div>
</article>
<aside class="sidebar">
<div class="sidebar-section">
<h3 class="sidebar-title">Table of Contents</h3>
<ul class="toc-list">
{''.join([f'<li><a href="#{section["id"]}">{section["title"]}</a></li>' for section in sections])}
</ul>
</div>
<div class="sidebar-section">
<h3 class="sidebar-title">About the Author</h3>
<p>This article was created by Sakshi Jadhav that combines research, writing, and design capabilities to produce comprehensive, informative content on cutting-edge topics.</p>
</div>
<div class="sidebar-section">
<h3 class="sidebar-title">Related Topics</h3>
<ul class="toc-list">
<li><a href="#">Latest Developments in {topic}</a></li>
<li><a href="#">Industry Perspectives on {topic}</a></li>
<li><a href="#">Research Advancements in {topic}</a></li>
<li><a href="#">Case Studies: {topic} in Action</a></li>
</ul>
</div>
</aside>
</div>
</div>
<footer>
<div class="container">
<div class="footer-content">
<div>
<p>&copy; {datetime.now().year} Professional Blog Hub</p>
<p>Created with AI Blog Generator</p>
</div>
<ul class="footer-links">
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Topics</a></li>
<li><a href="#">Contact</a></li>
</ul>
</div>
</div>
</footer>
</body>
</html>
"""
# Upload HTML file
upload_file(
path_or_fileobj=complete_html.encode(),
path_in_repo="index.html",
repo_id=space_name,
repo_type="space"
)
return f"https://huggingface.co/spaces/{space_name}", content_data.get("content", "")
except Exception as e:
return f"Error: {str(e)}", ""
# Gradio interface
with gr.Blocks(theme=gr.themes.Soft()) as app:
gr.Markdown("# 📄 Professional Blog Generator")
gr.Markdown("Create well-structured, professional blog posts with just a topic")
with gr.Row():
with gr.Column():
topic_input = gr.Textbox(label="Enter Blog Topic",
placeholder="e.g., Future of AI in Healthcare")
generate_btn = gr.Button("Generate Blog", variant="primary")
status = gr.Textbox(label="Status", interactive=False)
with gr.Column():
gr.Markdown("### Blog URL")
blog_link = gr.Markdown("Your blog link will appear here...")
gr.Markdown("### Preview")
blog_output = gr.Markdown()
generate_btn.click(
fn=create_hosted_blog,
inputs=topic_input,
outputs=[blog_link, blog_output]
)
if __name__ == "__main__":
app.launch(share=True)'''
import gradio as gr
import os
import time
import requests
import re
import uuid
import markdown
from datetime import datetime
from dotenv import load_dotenv
from huggingface_hub import HfApi, upload_file
import json
from functools import partial
import logging
# Set up logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
load_dotenv()
# Configuration
HF_TOKEN = os.getenv("HF_TOKEN")
if not HF_TOKEN:
logger.error("HF_TOKEN not found in .env file")
HF_USERNAME = "jsakshi"
HEADERS = {"Authorization": f"Bearer {HF_TOKEN}"}
# Simulated user authentication (replace with proper auth in production)
AUTHORIZED_USERS = {"admin": "password123"} # username: password
# Store edit history for undo/redo
edit_history = []
current_history_index = -1
def generate_initial_content(topic):
"""Generate initial blog content using Hugging Face API."""
logger.info(f"Generating content for topic: {topic}")
try:
prompt = f"""Create a detailed blog post about {topic} including:
- A compelling title and subtitle
- An introduction
- 3 main sections with descriptive headings
- A conclusion
Use an informative tone."""
response = requests.post(
"https://api-inference.huggingface.co/models/mistralai/Mistral-7B-Instruct-v0.2",
headers=HEADERS,
json={"inputs": prompt, "parameters": {"max_length": 2000}}
)
if response.status_code != 200:
logger.error(f"API request failed: {response.status_code} - {response.text}")
return f"Error: API request failed with status {response.status_code}"
return response.json()[0]['generated_text']
except Exception as e:
logger.error(f"Error generating content: {str(e)}")
return f"Error generating content: {str(e)}"
def generate_image(prompt):
"""Generate an image using Stable Diffusion API."""
try:
response = requests.post(
"https://api-inference.huggingface.co/models/stabilityai/stable-diffusion-xl-base-1.0",
headers=HEADERS,
json={"inputs": prompt}
)
if response.status_code == 200:
return response.content
logger.error(f"Image generation failed: {response.status_code} - {response.text}")
return None
except Exception as e:
logger.error(f"Error generating image: {str(e)}")
return None
def create_or_update_space(content_data, space_name=None, images=[]):
"""Create or update a Hugging Face Space with editable content."""
try:
api = HfApi(token=HF_TOKEN)
if not space_name:
space_id = f"blog-{uuid.uuid4().hex[:8]}"
space_name = f"{HF_USERNAME}/{space_id}"
api.create_repo(repo_id=space_name, repo_type="space", space_sdk="static", private=False)
logger.info(f"Created new space: {space_name}")
# Process content into editable sections
sections = re.split(r'(## .+)', content_data)
html_content = '<div class="editable-container">'
current_section = ""
for part in sections:
if part.strip():
if part.startswith('## '):
if current_section:
html_content += f'<div class="section-content" contenteditable="true">{markdown.markdown(current_section)}</div></div>'
html_content += f'<div class="section"><h2 class="editable-header" contenteditable="true">{part[3:]}</h2>'
current_section = ""
else:
current_section += part
if current_section:
html_content += f'<div class="section-content" contenteditable="true">{markdown.markdown(current_section)}</div></div>'
html_content += '</div>'
# Add images
image_html = ""
for i, img_path in enumerate(images):
image_html += f'<div class="image-container" draggable="true" data-index="{i}"><img src="{img_path}" class="editable-image" alt="Blog image" /><button class="delete-image">Delete</button></div>'
# Complete HTML with editing features
complete_html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Editable Blog</title>
<style>
.editable-container {{ max-width: 800px; margin: 20px auto; padding: 20px; }}
.editable-header {{ font-size: 1.5em; margin: 20px 0 10px; cursor: text; }}
.section-content {{ margin-bottom: 20px; cursor: text; }}
.image-container {{ position: relative; margin: 20px 0; }}
.editable-image {{ width: 100%; max-width: 500px; cursor: move; }}
.delete-image {{ position: absolute; top: 5px; right: 5px; }}
.editing-tools {{ position: fixed; top: 10px; left: 10px; background: white; padding: 10px; border: 1px solid #ccc; z-index: 1000; }}
[contenteditable]:focus {{ outline: 2px solid #2D68C4; }}
body {{ font-family: Arial, sans-serif; line-height: 1.6; }}
</style>
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div class="editing-tools" id="tools" style="display: none;">
<button onclick="document.execCommand('bold')">B</button>
<button onclick="document.execCommand('italic')">I</button>
<select onchange="document.execCommand('formatBlock', false, this.value)">
<option value="">Normal</option>
<option value="h1">H1</option>
<option value="h2">H2</option>
<option value="h3">H3</option>
</select>
<button onclick="saveChanges()">Save</button>
<button onclick="preview()">Preview</button>
<button onclick="undo()">Undo</button>
<button onclick="redo()">Redo</button>
</div>
{html_content}
{image_html}
<script>
let currentSpace = "{space_name}";
let images = {json.dumps(images)};
function saveChanges() {{
const content = document.querySelector('.editable-container').innerHTML;
fetch('/update', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ space: currentSpace, content: content, images: images }})
}}).then(() => alert('Saved!'));
addToHistory(content);
}}
function preview() {{
const content = document.querySelector('.editable-container').innerHTML;
const previewWindow = window.open('', '_blank');
previewWindow.document.write('<html><body>' + marked.parse(content) + '</body></html>');
}}
function addToHistory(content) {{
if (window.historyIndex < window.history.length - 1) {{
window.history.splice(window.historyIndex + 1);
}}
window.history.push(content);
window.historyIndex = window.history.length - 1;
localStorage.setItem('editHistory', JSON.stringify(window.history));
localStorage.setItem('historyIndex', window.historyIndex);
}}
window.history = JSON.parse(localStorage.getItem('editHistory') || '[]');
window.historyIndex = parseInt(localStorage.getItem('historyIndex') || '-1');
function undo() {{
if (window.historyIndex > 0) {{
window.historyIndex--;
document.querySelector('.editable-container').innerHTML = window.history[window.historyIndex];
localStorage.setItem('historyIndex', window.historyIndex);
}}
}}
function redo() {{
if (window.historyIndex < window.history.length - 1) {{
window.historyIndex++;
document.querySelector('.editable-container').innerHTML = window.history[window.historyIndex];
localStorage.setItem('historyIndex', window.historyIndex);
}}
}}
// Drag and drop images
document.querySelectorAll('.image-container').forEach(container => {{
container.addEventListener('dragstart', e => {{
e.dataTransfer.setData('text/plain', container.dataset.index);
}});
container.addEventListener('dragover', e => e.preventDefault());
container.addEventListener('drop', e => {{
e.preventDefault();
const fromIndex = e.dataTransfer.getData('text/plain');
const toIndex = container.dataset.index;
if (fromIndex !== toIndex) {{
const temp = images[fromIndex];
images[fromIndex] = images[toIndex];
images[toIndex] = temp;
saveChanges();
location.reload();
}}
}});
}});
// Delete image
document.querySelectorAll('.delete-image').forEach(btn => {{
btn.addEventListener('click', () => {{
const index = btn.parentElement.dataset.index;
images.splice(index, 1);
saveChanges();
location.reload();
}});
}});
// Autosave every 30 seconds
setInterval(saveChanges, 30000);
// Show tools on edit
document.querySelectorAll('[contenteditable]').forEach(el => {{
el.addEventListener('focus', () => document.getElementById('tools').style.display = 'block');
}});
</script>
</body>
</html>"""
# Upload HTML
upload_file(
path_or_fileobj=complete_html.encode(),
path_in_repo="index.html",
repo_id=space_name,
repo_type="space"
)
# Upload images if provided as bytes
for i, img in enumerate(images):
if isinstance(img, bytes):
upload_file(
path_or_fileobj=img,
path_in_repo=f"image_{i}.png",
repo_id=space_name,
repo_type="space"
)
images[i] = f"image_{i}.png"
logger.info(f"Updated space: {space_name}")
return f"https://huggingface.co/spaces/{space_name}"
except Exception as e:
logger.error(f"Error creating/updating space: {str(e)}")
return None
def authenticate(username, password):
"""Authenticate user against hardcoded credentials."""
if not username or not password:
logger.warning("Empty username or password provided")
return False
is_valid = username in AUTHORIZED_USERS and AUTHORIZED_USERS[username] == password
logger.info(f"Authentication attempt for {username}: {'Success' if is_valid else 'Failed'}")
return is_valid
def generate_and_edit(topic, username, password):
"""Generate blog and create editable space."""
logger.info(f"Starting generate_and_edit for topic: {topic}")
# Check authentication
if not authenticate(username, password):
return "Authentication failed: Incorrect username or password", "", "Error"
# Check HF_TOKEN
if not HF_TOKEN:
return "Authentication failed: HF_TOKEN not set in .env", "", "Error"
# Generate content
initial_content = generate_initial_content(topic)
if "Error" in initial_content:
return "Failed to generate content", initial_content, "Error"
# Generate images
image_prompts = [
f"Professional illustration about {topic}, clean design, minimalist style.",
f"Data visualization related to {topic}, infographic style."
]
images = []
for prompt in image_prompts:
img_data = generate_image(prompt)
if img_data:
images.append(img_data)
time.sleep(2) # Prevent rate limiting
# Create space
space_url = create_or_update_space(initial_content, images=images)
if not space_url:
return "Failed to create space", initial_content, "Error"
return space_url, initial_content, "Blog generated successfully"
# Gradio interface
with gr.Blocks(theme=gr.themes.Soft()) as app:
gr.Markdown("# 📝 Blog Editor")
gr.Markdown("Generate and edit professional blog posts with an intuitive interface")
with gr.Row():
with gr.Column():
username = gr.Textbox(label="Username", placeholder="admin")
password = gr.Textbox(label="Password", type="password", placeholder="password123")
topic_input = gr.Textbox(label="Blog Topic", placeholder="e.g., Future of AI")
generate_btn = gr.Button("Generate & Edit", variant="primary")
with gr.Column():
status = gr.Textbox(label="Status", interactive=False)
blog_link = gr.Markdown("Blog link will appear here...")
blog_preview = gr.Markdown(label="Preview", value="Content preview will appear here...")
generate_btn.click(
fn=generate_and_edit,
inputs=[topic_input, username, password],
outputs=[blog_link, blog_preview, status]
)
if __name__ == "__main__":
app.launch(share=True, debug=True)