Spaces:
Sleeping
Sleeping
File size: 4,335 Bytes
31fa536 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | """Gradio UI for the Blog Post Generator HF Space.
Inputs: topic, primary/secondary keyword, brief, and the user's HF token (password).
All paid AI calls are billed to that token. Outputs a live status log, a Markdown
preview, an image gallery, and a downloadable .docx.
"""
from __future__ import annotations
import re
import traceback
import gradio as gr
from pipeline import config, orchestrator
INTRO = """
# 📝 Blog Post Generator
Turn a topic + keywords + brief into a fully-written, **illustrated** blog post exported as `.docx`.
The pipeline: **search-term reasoning → self-hosted SearXNG web search → OpenPageRank
authority ranking → content extraction → LLM writing → FLUX.1-schnell images → vision
captioning → DOCX export.**
> All paid AI calls run through **Hugging Face Inference Providers using the token you
> enter below**, so they are billed to **you**. The token is used only for this run and
> is not stored.
"""
def _preview(markdown: str, images: list) -> str:
"""Replace [IMAGE:] markers with their caption text for a readable Markdown preview."""
imgs = [im for im in images if im.get("path")]
it = iter(imgs)
def repl(_m):
im = next(it, None)
cap = (im or {}).get("caption") or (im or {}).get("scene") or "image"
return f"\n> 🖼️ *{cap}*\n"
return re.sub(r"\[IMAGE:\s*.+?\]", repl, markdown, flags=re.DOTALL)
def generate(topic, primary, secondary, brief, hf_token, progress=gr.Progress()):
log_lines = []
gallery, docx_file, preview = [], None, ""
def status():
return "\n".join(f"- {ln}" for ln in log_lines)
try:
for frac, message, result in orchestrator.run(
hf_token, topic, primary, secondary, brief
):
progress(frac, desc=message)
log_lines.append(message)
if result.get("images"):
gallery = [
(im["path"], im.get("caption") or im.get("scene", ""))
for im in result["images"]
if im.get("path")
]
if result.get("markdown"):
preview = _preview(result["markdown"], result.get("images", []))
if result.get("docx_path"):
docx_file = result["docx_path"]
yield status(), preview, gallery, docx_file
except Exception as e: # noqa: BLE001 - show the user a clean error
log_lines.append(f"❌ **Error:** {e}")
traceback.print_exc()
yield status(), preview, gallery, docx_file
def build_ui() -> gr.Blocks:
with gr.Blocks(title="Blog Post Generator", theme=gr.themes.Soft()) as demo:
gr.Markdown(INTRO)
with gr.Row():
with gr.Column(scale=1):
topic = gr.Textbox(label="Topic", placeholder="e.g. Home composting for beginners")
primary = gr.Textbox(label="Primary keyword", placeholder="e.g. home composting")
secondary = gr.Textbox(label="Secondary keyword", placeholder="e.g. kitchen scraps")
brief = gr.Textbox(
label="Brief",
lines=5,
placeholder="Audience, angle, tone, must-cover points…",
)
hf_token = gr.Textbox(
label="Hugging Face token (billed to you)",
type="password",
placeholder="hf_...",
)
run_btn = gr.Button("Generate blog post", variant="primary")
with gr.Column(scale=2):
status_box = gr.Markdown(label="Progress")
docx_out = gr.File(label="Download .docx")
gallery = gr.Gallery(label="Generated images", columns=2, height=320)
preview = gr.Markdown(label="Preview")
run_btn.click(
fn=generate,
inputs=[topic, primary, secondary, brief, hf_token],
outputs=[status_box, preview, gallery, docx_out],
)
gr.Markdown(
f"Models: writer `{config.MODEL_WRITER}` · reasoning `{config.MODEL_REASONING}` · "
f"images `{config.MODEL_IMAGE}` · vision `{config.MODEL_VISION}`."
)
return demo
if __name__ == "__main__":
build_ui().queue().launch(server_name="0.0.0.0", server_port=7860)
|